Integration Quickstart
The guide a cold developer follows to add Treatink personalization to a storefront in under a day.
Everything here runs in fixtures mode — a bundled demo catalog, no backend and no API keys —
which is the default. Flip to mode: 'live' when your channel is provisioned.
1. Install
The SDK is distributed as a versioned package attached to GitHub Releases.
For bundled apps: install the release tarball directly:
npm install https://github.com/treatink/treatink-sdk/releases/download/v0.1.1/treatink-sdk-0.1.1.tgz
Alternatively, download treatink-sdk-0.1.1.tgz from the
releases page and install the local file:
npm install ./treatink-sdk-0.1.1.tgz
Either way the package name is unchanged:
import { Treatink } from '@treatink/sdk';
Script tag (no build step): each release also attaches the browser bundle — index.js plus
its companion chunk files (chunk-*.js, designer-*.js, fixture-dataset-*.js,
font-data-*.js, heic2any-*.js). index.js imports the chunks by relative path (the designer
UI is lazy-loaded on first open), so host the whole set together in one directory and load
only index.js, pinned with its SRI hash from the release notes:
<script
type="module"
src="/vendor/treatink-sdk/v0.1.1/index.js"
integrity="sha384-…"
crossorigin="anonymous"
></script>
Per-file SRI hashes for the chunks are also published in the release notes if you want to pin them via an import map.
The browser bundle is publishable-key only. The one secret-key operation — submitting the
order — lives in a separate server entry (@treatink/sdk/server, §4) and is never importable from
browser code.
2. The client flow (fixtures mode, copy-runnable)
Save this as an HTML file, serve it alongside the SDK bundle, and open it — the modal designer runs end to end with no backend.
<script type="module">
import { Treatink } from '@treatink/sdk';
// Your storefront's add-to-cart. In this fixtures demo it just records the line.
function addToCart(line) {
console.log('add to cart', line);
}
const tk = Treatink.init({
apiKey: 'pk_test_quickstart',
channel: 'petshop.example.com',
mode: 'fixtures',
});
// Open the modal designer. In fixtures mode the catalog and cutouts come from the bundled
// demo dataset (no backend, no keys) — so this snippet is copy-runnable exactly as written.
tk.designer.open({
sku: 'SSGTTBC',
cutoutLabelId: 'cut_fx_00000001', // optional: preselect a cutout (omit to let the shopper choose)
onComplete(result) {
// The shopper saved. Add the design to your cart: keep result.draftId as the line-item
// reference and result.previewUrl (a local object URL) for the cart thumbnail.
addToCart({ sku: result.sku, draftId: result.draftId, previewUrl: result.previewUrl });
},
});
</script>
What happens inside onComplete (see DesignerResult): the two artwork assets
(source + rendered) have already uploaded, a reference-only draft is written to
localStorage (never any image bytes), and result.previewUrl is a local object URL of the
display composite. Persist result.draftId on the cart line — it is both the design's handle and
the order idempotency token.
3. Build the order body (browser, pure)
At checkout, turn the saved draft(s) into the order wire body. buildPayload is pure — no network,
nothing secret — and pulls variant_id, the asset ids, and the personalization block from the
draft by draftId.
const payload = tk.orders.buildPayload({
externalOrderId: 'partner-1001', // your order id — also the default Idempotency-Key
displayOrderNumber: '#1001',
currency: 'USD', // the API accepts USD only
recipient: { name: 'Sam Rivera', email: 'shopper@example.com' }, // email or phone required
destination: {
addressLine1: '1 Main St',
city: 'Austin',
region: 'TX',
postalCode: '78701',
countryCode: 'US',
},
amounts: { subtotalCents: 999, discountCents: 0, shippingCents: 295, taxCents: 0, totalCents: 1294 },
lines: [{ externalLineItemId: 'li-1', draftId, quantity: 1, unitPriceCents: 999 }],
});
The result matches the order schema in the wire contract field-for-field. Send it to your own server — the browser never holds a secret key.
4. Submit the order (server, secret key)
On your server (Node ≥ 18), submit with your secret key. Re-posting the same payload with the
same Idempotency-Key (default: external_order_id) replays the original order, so retries are
safe.
import { submitOrder } from '@treatink/sdk/server';
const order = await submitOrder(payload, {
secretKey: process.env.TREATINK_SECRET_KEY, // sk_test_… | sk_live_…
channel: 'petshop.example.com',
});
// order.status === 'received'; order.externalOrderId === 'partner-1001'
:::info Idempotency
POST /v1/orders requires an Idempotency-Key header — submitOrder sends it automatically
(default = external_order_id). Reusing the same key with a different body returns a 409
idempotency_conflict. Staging is available at https://staging.treatinkapi.com via apiBaseUrl.
:::
:::note ESM-only
The package ships ES modules only. If your server code is CommonJS, load it with a dynamic
import — const { submitOrder } = await import('@treatink/sdk/server') — or run your server as
ESM ("type": "module").
:::
5. Recommended CSP & SRI
Ship a Content-Security-Policy that allows the SDK's API/storage/image hosts. With the self-hosted
script-tag install from §1, script-src 'self' already covers the bundle — pin it with the
Subresource-Integrity hash published in each release's notes:
Content-Security-Policy:
script-src 'self';
connect-src 'self' treatinkapi.com <storage-host>;
img-src 'self' cdn.treatink.com blob: data:;
<script
type="module"
src="/vendor/treatink-sdk/v0.1.1/index.js"
integrity="sha384-…"
crossorigin="anonymous"
></script>
async/defer are safe; the SDK never calls document.write. Confirm the storage host with
Treatink before publishing your CSP.
5.1 Privacy disclosure
Include this in your storefront's privacy notice — it reflects exactly how the SDK handles a shopper's photo:
- Where photos go. The photo a shopper selects is uploaded only to Treatink infrastructure (the API host and its presigned storage host) over TLS, and only at save time — nothing is sent while they are still editing.
- No third-party requests. The SDK makes no calls to analytics, trackers, external fonts, or any other origin. The only network destinations are the hosts in the CSP above.
- No image bytes stored on the device. Drafts saved to
localStoragehold references only (asset ids, layout metadata) — never the photo bytes, never ablob:/data:URL. - Shared-device cleanup. Call
tk.drafts.clear()to remove all saved draft references (e.g. on a kiosk or shared computer). Deleting a draft never touches already-submitted orders.
6. Theming & copy
Pass theme and copy to Treatink.init — both are fully overridable (see
Theming). Theme values become --tk-* CSS variables; every user-visible
string has a copy key.
Treatink.init({
apiKey: 'pk_test_quickstart',
channel: 'petshop.example.com',
theme: { primary: '#8EA0F6', accent: '#EA8D00', borderRadius: '15px' },
copy: { headerTitle: 'Personalize Your Product', saveButton: 'Save Customization' },
});
7. API reference
The full, frozen TypeScript surface is documented in SDK types. Summary of what a partner touches:
7.1 Entry — Treatink.init(config)
| Field | Type | Notes |
|---|---|---|
apiKey | string | publishable only (pk_test_… / pk_live_…); an sk_… key throws key_scope_violation |
channel | string | your registered storefront hostname |
mode | 'live' | 'fixtures' | default 'fixtures' |
apiBaseUrl | string? | staging override; default https://treatinkapi.com |
theme / copy | objects | §6 |
maxPersonalizationLength | number? | text cap fallback (default 20) |
7.2 Instance namespaces
| Namespace | Method | Purpose |
|---|---|---|
products | list(params?) / get(sku) | catalog; get resolves SKU → variant |
templates | list({ sku }) | cutout-labels for a product |
artwork | upload({ role, file }) | two-step asset upload (source/rendered) — the designer calls this for you |
designer | open(options) / close() | the modal designer |
drafts | list() / get(id) / delete(id) / clear() | reference-only drafts (no image bytes) |
orders | buildPayload(input) | pure order-body assembly (§3) |
on(event, handler) | — | 'designer:open' | 'designer:close' | 'draft:saved' | 'error' |
fixtures | failNext(op, err) / setLatency(ms) | present only in mode:'fixtures' |
7.3 designer.open(options) → onComplete(result)
DesignerOptions: sku (required), draftId? (re-open a saved draft — restores metadata; the
shopper re-selects the photo), personalizationText?, cutoutLabelId?, onComplete? /
onError? / onClose?.
DesignerResult: draftId, sku, variantId?, cutoutLabelId, personalizationText?,
petNamePosition?, previewUrl (local object URL), artwork { sourceAssetId, renderedAssetId },
transform, labelZone, lowRes.
7.4 Server — submitOrder(payload, options)
options: secretKey (sk_…), channel, apiBaseUrl?, idempotencyKey? (defaults to the
payload's external_order_id). Returns { id, status, externalOrderId, displayOrderNumber }.
Never import this from browser code.
Errors everywhere are TreatinkError { code, status?, param?, requestId? } — see
Errors.