Skip to main content

Server API — @treatink/sdk/server

Everything secret-key lives in the server entry (Node ≥ 18, ESM), never importable from browser code. Four capabilities: order submission (see Quickstart §4), the AI generation surface, and webhook verification.

import {
submitOrder,
listGenerationStyles,
createGeneration,
getGeneration,
verifyWebhookEvent,
webhookDeliveryId,
} from '@treatink/sdk/server';

All API-calling functions take the same options: { secretKey, apiBaseUrl? }sk_test_… / sk_live_… only (a publishable key throws key_scope_violation before any network call). Staging: apiBaseUrl: 'https://staging.treatinkapi.com'.

AI image generation

Generation spends provider budget per call, which is why it is secret-key only — never expose it to browsers. The shopper-facing pattern is: the browser uploads the source photo (publishable path), your server triggers the generation.

const styles = await listGenerationStyles({ secretKey });
// [{ style: 'superhero', name: 'Superhero' }, …]

const generation = await createGeneration(
{ sourceAssetId: 'ast_…', style: 'superhero' }, // and/or prompt: '…' (1–1000 code points)
{ secretKey },
);
// Synchronous — resolves with the completed record (bounded ~60 s server-side).
// generation.status: 'completed' | 'partially_completed' | 'failed'
for (const variant of generation.variants) {
if (variant.assetId) {
// A final `ast_` source asset — usable anywhere an uploaded asset is.
} else {
// variant.error: { code: 'provider_refused' | 'provider_failed', message }
}
}

const again = await getGeneration(generation.id, { secretKey }); // re-read after a dropped connection

Failure semantics are honest: partially_completed means some variants succeeded — check each. Whole-set failures reject with generation_refused (422), generation_failed (502), or generation_quota_exceeded (409, at most 2 in progress per account and mode).

Webhook verification

The receiver recipe as one call: v1 HMAC-SHA256 over timestamp.body raw bytes, constant-time comparison, and a replay-tolerance window (default 300 s). Always pass the raw request body — never a re-serialized parse.

import { verifyWebhookEvent, webhookDeliveryId } from '@treatink/sdk/server';

app.post('/webhooks/treatink', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = verifyWebhookEvent(req.body, req.headers, {
secret: process.env.TREATINK_WEBHOOK_SECRET, // the endpoint's whsec_…, shown once at creation
});
} catch {
return res.status(400).end(); // invalid signature/timestamp — the platform retries
}
// Deduplicate on the stable event id (redeliveries are byte-identical):
const deliveryId = webhookDeliveryId(req.headers); // === event.id
// event.type: 'order.received' | 'order.in_production' | 'order.shipped'
// | 'order.rejected' | 'order.cancelled' | 'shipment.created'
// event.data: { order_id, status } (+ shipment_id for shipment.created)
res.status(204).end();
});

Deliveries are dispatched on a scheduled cadence — expect a webhook about a minute after the event, retried with exponential backoff (one minute doubling to a one-hour cap, up to 10 attempts) on non-2xx responses. Order events by event.occurred_at, not arrival. Verification failures throw TreatinkError with webhook_signature_invalid, webhook_timestamp_invalid, or webhook_payload_invalid.