Public Types
The public TypeScript surface of @treatink/sdk. This is the contract — the package's
types.ts matches it exactly, and no public symbol exists that isn't here.
1. Entry
const tk: Treatink = Treatink.init(config: TreatinkConfig);
interface TreatinkConfig {
apiKey: string; // publishable only: 'pk_test_…' | 'pk_live_…'.
// 'sk_…' (or any non-pk_) throws key_scope_violation (§7)
channel: string; // registered storefront hostname (e.g. 'petshop.example.com')
mode?: 'live' | 'fixtures'; // default 'fixtures'
apiBaseUrl?: string; // staging: https://staging.treatinkapi.com; default https://treatinkapi.com
theme?: ThemeConfig;
copy?: Partial<CopyStrings>;
maxPersonalizationLength?: number; // text-cap fallback when a template has no maxTextLength (§5; default 20)
debug?: boolean;
}
2. Instance
interface Treatink {
products: ProductsApi;
templates: TemplatesApi; // cutout-labels
artwork: ArtworkApi; // two-step asset upload
designer: DesignerApi;
drafts: DraftsApi;
orders: OrdersApi; // buildPayload only (browser, no network)
on(event: TreatinkEvent, handler: (payload: unknown) => void): () => void;
fixtures?: FixturesApi; // present only in mode:'fixtures'
}
type TreatinkEvent = 'designer:open' | 'designer:close' | 'draft:saved' | 'error';
No
sessionsnamespace — the backend is asset-based. The designer's save orchestrates assets internally.
3. Namespaces
interface ProductsApi {
list(params?: { limit?: number; cursor?: string }): Promise<Page<Product>>;
get(sku: string): Promise<Product>; // resolves SKU → its variant
}
interface TemplatesApi { // cutout-labels
list(params: { sku: string; limit?: number; cursor?: string }): Promise<Page<Template>>;
}
interface ArtworkApi {
// Runs the full two-step flow (declare → PUT bytes → finalize) and returns the final asset.
upload(input: { role: AssetRole; file: Blob; sha256?: string }): Promise<Asset>;
}
type AssetRole = 'source' | 'rendered';
interface DesignerApi {
open(options: DesignerOptions): void;
close(): void;
}
interface DraftsApi {
list(): DraftRecord[];
get(draftId: string): DraftRecord | null;
delete(draftId: string): void;
clear(): void;
}
interface OrdersApi {
buildPayload(input: BuildPayloadInput): OrderPayload; // pure; no network, nothing secret
}
interface FixturesApi {
failNext(op: string, error: { status: number; code: string }): void;
setLatency(ms: number): void;
}
4. Designer options & result
interface DesignerOptions {
sku: string; // required
draftId?: string; // re-open a saved draft (re-save creates a new draft)
personalizationText?: string; // prefill
cutoutLabelId?: string; // preselect a cutout
onComplete?(result: DesignerResult): void;
onError?(error: TreatinkError): void;
onClose?(): void;
}
interface DesignerResult {
draftId: string; // UUID v4 — also the idempotency token
sku: string;
variantId?: string; // resolved 'var_…'
cutoutLabelId: string; // 'cut_…'
personalizationText?: string | null;
petNamePosition?: PetNamePosition;
previewUrl: string; // LOCAL object URL of the DISPLAY composite —
// product mockup + label in label_zone; not uploaded
artwork: { sourceAssetId: string; renderedAssetId: string }; // 'ast_…' ids (no URLs)
transform: Transform;
labelZone: LabelZone;
lowRes: boolean;
}
type PetNamePosition = 'default' | 'top' | 'upper' | 'bottom';
// Transform is in 900×1200 print-canvas pixels and is self-contained for print re-render.
interface Transform { x: number; y: number; scale: number; rotation: number } // rotation is always 0 currently
interface LabelZone { x: number; y: number; width: number; height: number } // normalized
5. Catalog models (SDK-normalized)
Wire shapes are documented in the wire contract.
interface Product {
sku: string; variantId: string; productId: string;
title: string; description?: string;
animalType?: 'cat' | 'dog' | 'horse';
category?: string; productType?: string; status: string;
priceCents: number; currency: string;
images: { catalogImageUrl: string; regulatoryLabelUrl?: string };
labelZone: LabelZone | null; // null for no-zone products (edge case)
}
interface Template { // a cutout-label
cutoutLabelId: string; // 'cut_…'
title: string;
category: 'standard' | 'holidays' | 'birthdays' | 'occasions';
theme: 'light' | 'dark';
petNamePosition: PetNamePosition;
tags: string[];
maskUrl: string; // the cutout PNG (alpha = the mask)
canvas: { width: 900; height: 1200 };
opening: OpeningGeometry; // precomputed alpha geometry — not decoded at runtime
maxTextLength?: number; // personalization-text cap. NOT in the backend model —
// SDK falls back to config `maxPersonalizationLength`
// (default 20); visual auto-shrink still applies
}
interface Asset { // finalized artwork asset
id: string; // 'ast_…'
role: AssetRole;
status: 'final';
contentType: string; width: number; height: number; sha256: string;
}
interface Page<T> { data: T[]; hasMore: boolean; nextCursor: string | null }
// Precomputed alpha geometry — consumed as-is; never decoded at runtime.
interface OpeningGeometry {
alphaThreshold: number;
fullyTransparentBounds: RectWithCount;
centerTransparentComponent: RectWithCount & { touchesCanvasEdge: boolean };
largestSafeTransparentRectangle: RectWithCount;
}
interface RectWithCount {
pixels: { x: number; y: number; width: number; height: number };
normalized: { x: number; y: number; width: number; height: number };
pixelCount?: number;
}
6. Draft record (localStorage — references only)
// keys: treatink:v1:<channel>:index and treatink:v1:<channel>:draft:<draftId>
interface DraftRecord {
draftId: string; // UUID v4 — idempotency token
createdAt: string; updatedAt: string;
channel: string;
product: { sku: string; variantId?: string };
cutout: { cutoutLabelId: string; petNamePosition?: PetNamePosition };
personalizationText?: string | null;
transform: Transform;
labelZone: LabelZone;
artwork: { sourceAssetId: string; renderedAssetId: string }; // remote refs (ids). NO image bytes, NO URLs
status: 'completed' | 'ordered';
}
Re-open behavior. designer.open({ draftId }) restores all metadata (cutout,
transform, text, zone). It cannot re-hydrate the original photo pixels: no image bytes are
stored, and a publishable key cannot GET the uploaded source asset back. So re-opening a draft
restores the layout and asks the shopper to re-select the photo to edit it; re-saving creates a
fresh draft + assets. Known limitation — documented, not a bug.
7. Errors
class TreatinkError extends Error {
code: string; // API codes + SDK-local: key_scope_violation,
// unsupported_file_type, upload_failed
status?: number;
param?: string;
requestId?: string;
}
See Errors for the full code list and the wire envelope.
8. Theme, copy, order payload
// Defaults = the EXACT Treatink store palette.
// Derived tokens resolve explicit-wins: set them for full control, or override only the base
// (primary/accent/borderRadius) and the SDK derives coherent shades via color-mix()/min().
interface ThemeConfig { // → --tk-* CSS vars
primary?: string; // default #a99cdf (purple)
primaryStrong?: string; // default #8c7ec2 (purple-darker); derives from primary
panelBackground?: string; // default #e2e6ff (purple-light); derives from primary
accent?: string; // default #ffa518 (orange)
accentHover?: string; // default #dd9133 (orange-hover); derives from accent
headerBackground?: string; // default #F26B1D (SDK modal chrome)
headerText?: string;
surface?: string;
surfaceAlt?: string; // default #F6F6FC (purple-extra-light); derives from primary
borderRadius?: string; // default '20px' (cards)
buttonRadius?: string; // default '15px' (filled buttons); min()-derives from borderRadius
controlRadius?: string; // default '10px' (chips/thumbs/inputs); min()-derives from borderRadius
fontFamily?: string; // Montserrat + system fallback
overlayColor?: string;
zIndex?: number; // default 2147483000
logo?: string | false;
}
interface CopyStrings { // every user-visible designer string — all overridable
headerTitle: string; // 'Personalize Your Product'
closeLabel: string;
uploadPrompt: string; // "Drag your pet's photo here\nand start personalizing!"
uploadButton: string; // 'Or Select Image'
zoomInLabel: string; zoomOutLabel: string; // compat only — no −/+ buttons
zoomSliderLabel: string;
categoryAll: string; // 'Browse All' (the button under the pager)
imageControlsLabel: string; // 'Image Controls'
rotateLeftLabel: string; rotateRightLabel: string; deleteImageLabel: string;
cutoutsLabel: string; // 'Choose Your Background'
browseAllTitle: string; // 'Browse All Backgrounds'
searchPlaceholder: string; // 'Search'
noCutoutsFound: string; // 'No backgrounds found'
personalizationTextLabel: string; // 'Include Pet Name on Label'
personalizationTextPlaceholder: string; // 'Pet Name'
lowResWarning: string;
saveButton: string; // 'Save Customization'
savingLabel: string; saveErrorRetry: string;
genericError: string;
}
// Mirrors the REAL POST /v1/orders contract.
// The wire is strict: nullable fields must be present — buildPayload emits explicit nulls.
interface BuildPayloadInput {
externalOrderId: string; // unique per partner+mode; default Idempotency-Key
displayOrderNumber?: string | null;
currency: 'USD'; // the API accepts USD only
recipient: { name: string; email?: string | null; phone?: string | null }; // >=1 contact
destination: {
addressLine1: string; addressLine2?: string | null;
city: string; region?: string | null; postalCode?: string | null;
countryCode: string; // ISO 3166-1 alpha-2
};
fulfillment?: { instructions?: string | null }; // delivery_method is always ship_to_recipient
amounts: { subtotalCents: number; discountCents: number; shippingCents: number;
taxCents: number; totalCents: number };
lines: Array<{
externalLineItemId: string; // REQUIRED + unique (wire rule)
draftId: string; // pulls variant_id, asset ids, cutout id, pet_name
quantity: number; // 1..100
unitPriceCents: number;
subtotalCents?: number; // default quantity x unitPriceCents
}>;
}
// OrderPayload = the exact wire body in the wire contract (snake_case, personalization block).