Developers · API v1.0.0

Run your POD shop from code, or from your AI.

Everything in the app (templates, previews, print files, products across suppliers, mockups, Etsy and Shopify listings, the order queue, Radar and analytics) is one REST API and one MCP server. On every plan, not just Enterprise.

Quickstart

Create an API key in Settings → Developers, pick its scopes, then:

Who am I
curl https://api.pod-os.com/v1/me \
  -H "Authorization: Bearer $POD_API_KEY"
Turn a Radar card into products
# 1. This week's opportunities
curl "https://api.pod-os.com/v1/radar/cards?limit=3" -H "Authorization: Bearer $POD_API_KEY"

# 2. Template from a starter (free) or a prompt (AI credits)
curl -X POST https://api.pod-os.com/v1/templates \
  -H "Authorization: Bearer $POD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":{"type":"prompt","prompt":"watercolor pet memorial ornament","variations":4}}'

# 3. Freeze a version, then put it on blanks from several suppliers
curl -X POST https://api.pod-os.com/v1/templates/tpl_123/versions -H "Idempotency-Key: $(uuidgen)" ...
curl -X POST https://api.pod-os.com/v1/products -H "Idempotency-Key: $(uuidgen)" ... \
  -d '{"template_id":"tpl_123","targets":[{"product_type":"ornament-round"},{"product_type":"mug-11oz","supplier":"gelato"}]}'

Connect an AI agent

The MCP server lives at https://mcp.pod-os.com/mcp. It speaks Streamable HTTP and OAuth 2.1 (PKCE, dynamic client registration), so clients that support remote MCP connect with just the URL. Clients without OAuth send an API key as a bearer token. Tools that publish, approve orders, refund, message buyers or spend AI credits take a confirm argument. Without confirm: true they return a preview and change nothing, so the agent has to ask you first.

Claude

Settings → Connectors → Add custom connector → paste the URL → Connect. Approve the scopes on the POD OS consent screen. Works in Claude.ai, Claude Desktop and Claude Code:

claude mcp add --transport http pod-os https://mcp.pod-os.com/mcp

ChatGPT

Settings → Apps & Connectors → Advanced → Developer mode, then Create → MCP server URL https://mcp.pod-os.com/mcp, authentication OAuth. The search and fetch tools make the workspace usable in deep research too.

Gemini CLI

~/.gemini/settings.json
{
  "mcpServers": {
    "pod-os": {
      "httpUrl": "https://mcp.pod-os.com/mcp",
      "headers": { "Authorization": "Bearer $POD_API_KEY" }
    }
  }
}

Grok (xAI API)

Remote MCP tool
{
  "type": "mcp",
  "server_url": "https://mcp.pod-os.com/mcp",
  "server_label": "pod-os",
  "authorization": "Bearer $POD_API_KEY"
}

Prompts included: Launch a product from an idea and Review today's orders. Resources: pod://templates/…, pod://products/…, pod://orders/…, the catalog and the agent guide.

Keys and scopes

Keys belong to one workspace and carry scopes. Send Authorization: Bearer pod_live_…. Keys are stored hashed; you see the secret once. OAuth connectors get at most the scopes you tick, and publishing and order approval are separate scopes so you can hand an agent “draft everything, publish nothing”.

workspace:readSee the workspace name, plan and the scopes of this key.
templates:readList and read templates, versions and starter designs.
templates:writeCreate, edit and version templates (prompt-generated templates use AI credits).
renders:writeRender previews and queue print files.
products:readList products and the supplier catalog.
products:writeCreate products across suppliers, auto-fit designs and render mockups.
listings:readRead listing drafts and published listings.
listings:writeGenerate and edit listing copy.
listings:publishPublish listings to Etsy and Shopify (public, may incur marketplace fees).
orders:readList and read orders and their pre-flight results.
orders:writeApprove, reject and send proofs for orders (approval sends paid work to suppliers).
radar:readRead Radar opportunity cards.
analytics:readRead sales and conversion analytics.
webhooks:manageCreate, list and delete webhook endpoints.

Conventions

Idempotency

Every POST, PATCH and DELETE needs an Idempotency-Key header (8–255 chars, a UUID works). Retrying with the same key and body returns the first response with Idempotent-Replayed: true. The same key with a different body returns 422 idempotency_key_reused. Keys last 24 hours, per workspace.

Pagination

Lists return { data, has_more, next_cursor }, newest first. Pass next_cursor as cursor. limit is 1–100, default 20. Cursors are keyset-based, so pages stay stable while new rows arrive.

Rate limits

Per key (120 requests a minute by default, set per key). Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; a 429 adds Retry-After.

Errors

{ "error": { "code": "insufficient_scope", "message": "This key is missing scope(s): orders:write",
             "details": { "missing": ["orders:write"] }, "request_id": "req_…" } }

Codes: unauthorized, insufficient_scope, not_found, validation_error, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, rate_limited, conflict, internal_error.

Async jobs

Print renders, mockups and publishing return 202 with a job. Poll it or subscribe to the webhook. Print files are content-addressed: the same template version and inputs never render twice.

Webhooks

Events: template.created, template.version_created, render.succeeded, render.failed, product.created, mockups.succeeded, listing.published, listing.failed, order.created, order.approved, order.rejected, order.proof_sent.

Each delivery is signed: Pod-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + rawBody)>. Return a 2xx within 10 seconds; failures retry after 30 s, 2 min, 10 min and 1 h. De-duplicate by Pod-Event-Id.

Verify in Node
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret: string, rawBody: string, header: string) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // replay window
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1 ?? "", "hex");
  return got.length === expected.length && timingSafeEqual(got, expected);
}

Workspace

Who am IGET /v1/me

Returns the workspace this key belongs to, its plan, remaining AI credits, connected channels and suppliers, and the scopes this key holds. Call it first to learn what you're allowed to do.

any keyMCP: get_workspace

Returns 200 Me

Templates

List starter templatesGET /v1/starters

Proven starter designs (family ornament, pet portrait, teacher tumbler…). Pass a starter id to create_template with source.type=starter.

templates:readMCP: list_starter_templates

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
nichestring ≤80query. Filter by niche, e.g. christmas.
product_typestring ≤64query. Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.

Returns 200 { data: Starter[], has_more, next_cursor }

List templatesGET /v1/templates

The workspace's templates, newest first. Filter by text or status. Cursor-paginated.

templates:readMCP: list_templates

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
qstring ≤120query. Search name and tags.
status"draft" | "generating" | "ready" | "archived"query.

Returns 200 { data: Template[], has_more, next_cursor }

Create a templatePOST /v1/templates

Create a template from a design prompt (AI generates artwork variations; uses one AI credit per variation and finishes in the background: status goes generating → draft), from a starter, from a PSD URL, or blank for a product type. Next: update_template to pick a variation and set fields, then create_template_version.

templates:writeMCP: create_template_from_promptMCP: create_template_from_starterMCP: import_template_from_psdMCP: create_blank_templateIdempotency-Key required

Body

namestring ≤120Defaults to a name derived from the source.
source *object | object | object | objectOne of the variants below.
  • "prompt": prompt, style, variations
  • "starter": starter_id
  • "psd": file_url
  • "blank": product_type
product_typesstring ≤64[]Products this design targets. Defaults from the source.

Returns 201 Template

Get a templateGET /v1/templates/{template_id}

One template with its personalization fields, product types, AI variations and latest version number.

templates:readMCP: get_template

Parameters

template_id *stringpath. ID, prefixed tpl_.

Returns 200 Template

Update a templatePATCH /v1/templates/{template_id}

Edit the working draft: name, personalization fields, product types, tags, chosen AI variation, status. Live products keep their pinned version until you cut a new one.

templates:writeMCP: update_templateIdempotency-Key required

Parameters

template_id *stringpath. ID, prefixed tpl_.

Body

namestring ≤120
personalization_fieldsobject[]
product_typesstring ≤64[]
tagsstring ≤40[]
selected_variationinteger 0–7Pick one AI variation as the artwork.
status"ready" | "archived"

Returns 200 Template

Cut a template versionPOST /v1/templates/{template_id}/versions

Freeze the current draft as an immutable, numbered version. Print renders and products pin a version, so edits never change what's already selling.

templates:writeMCP: create_template_versionIdempotency-Key required

Parameters

template_id *stringpath. ID, prefixed tpl_.

Body

notestring ≤500Change note, e.g. "bigger name text".

Returns 201 TemplateVersion

List template versionsGET /v1/templates/{template_id}/versions

All immutable versions of a template, newest first.

templates:readMCP: list_template_versions

Parameters

template_id *stringpath. ID, prefixed tpl_.

Returns 200 { data: TemplateVersion[] }

Renders

Render a previewPOST /v1/templates/{template_id}/preview

Synchronous low-res preview (≤1024 px) of a template with buyer inputs, plus pre-flight warnings (overflow, low-res photo, profanity, trademark). Free; use it to check a design before cutting a version or queueing a print file.

renders:writeMCP: render_preview

Parameters

template_id *stringpath. ID, prefixed tpl_.

Body

inputsobjectBuyer choices keyed by personalization field key, e.g. {"name": "Luna"}. Photo fields take an HTTPS URL. Default {}.
versioninteger 1–9007199254740991Template version. Defaults to the working draft.
product_typestring ≤64Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.
widthinteger 64–1024Preview width in px (low-res, max 1024). Default 512.

Returns 200 Preview

Queue a print file renderPOST /v1/renders

Queue a full-resolution print file for a pinned template version, product type and surface. Returns immediately (202) with a job; poll get_render or listen for the render.succeeded webhook. Identical inputs return the same content-addressed file.

renders:writeMCP: queue_print_renderIdempotency-Key requiredasync (202)

Body

template_id *stringID, prefixed tpl_.
version *integer 1–9007199254740991Pinned template version. Print files always render from an immutable version.
inputsobjectBuyer choices keyed by personalization field key, e.g. {"name": "Luna"}. Photo fields take an HTTPS URL. Default {}.
product_type *string ≤64Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.
surfacestring ≤40Print surface, e.g. front, back, sleeve, wrap. Default "front".
format"png" | "pdf" Default "png".

Returns 202 RenderJob

Get a render jobGET /v1/renders/{render_id}

Status of a print render. file_url is set when status is succeeded.

renders:writeMCP: get_print_render

Parameters

render_id *stringpath. ID, prefixed rnd_.

Returns 200 RenderJob

Products

List the product catalogGET /v1/catalog

Product types you can sell, with each supplier's base cost, delivery time, regions and print surfaces (size in mm, DPI).

products:readMCP: list_catalog

Returns 200 { data: CatalogItem[] }

List productsGET /v1/products

Products (a template on a specific blank from a specific supplier), newest first.

products:readMCP: list_products

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
template_idstringquery. ID, prefixed tpl_.
supplier"printify" | "printful" | "gelato" | "own"query. Print partner. own = the seller prints in-house.

Returns 200 { data: Product[], has_more, next_cursor }

Create products across suppliersPOST /v1/products

Put one template on many blanks at once (e.g. tee from Printify, mug from Gelato). Each target becomes a product; auto_fit fits the design to every surface. Nothing is published or ordered yet. Next: create_mockups, then generate_listing.

products:writeMCP: create_productsIdempotency-Key required

Body

template_id *stringID, prefixed tpl_.
template_versioninteger 1–9007199254740991Defaults to the latest version.
targets *object[]
auto_fitbooleanFit the design to every surface of every product. Default true.

Returns 201 { data: Product[] }

Get a productGET /v1/products/{product_id}

One product with variants, price, base cost, fit report and mockups.

products:readMCP: get_product

Parameters

product_id *stringpath. ID, prefixed prd_.

Returns 200 Product

Auto-fit the designPOST /v1/products/{product_id}/auto-fit

Re-fit the design to the product's print surfaces (contain, cover or stretch). The fit report flags clipping and effective DPI.

products:writeMCP: auto_fit_productIdempotency-Key required

Parameters

product_id *stringpath. ID, prefixed prd_.

Body

mode"contain" | "cover" | "stretch" Default "contain".
surfacesstring[]Defaults to every surface of the product.

Returns 200 Product

Mockups

Render mockupsPOST /v1/products/{product_id}/mockups

Render mockups from the real print file: flat, lifestyle, model, gift and close-up scenes per color. media_kit=true also renders the lead image, how-it-works panel and a 12-second video (5 AI credits). Async (202): poll get_mockup_job or wait for mockups.succeeded.

products:writeMCP: create_mockupsIdempotency-Key requiredasync (202)

Parameters

product_id *stringpath. ID, prefixed prd_.

Body

scenes"flat" | "lifestyle" | "model" | "gift" | "closeup"[] Default ["flat","lifestyle"].
colorsstring ≤40[]Product colors to render. Defaults to the first variant color.
media_kitbooleanAlso render lead image with badges, how-it-works panel and a 12-second video (uses AI credits). Default false.

Returns 202 MockupJob

Get a mockup jobGET /v1/mockups/{mockup_job_id}

Status and image URLs of a mockup job.

products:readMCP: get_mockup_job

Parameters

mockup_job_id *stringpath. ID, prefixed mkj_.

Returns 200 MockupJob

Listings

List listingsGET /v1/listings

Listing drafts and published listings, newest first.

listings:readMCP: list_listings

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
status"draft" | "publishing" | "published" | "failed"query.
channel"etsy" | "shopify"query. Sales channel.

Returns 200 { data: Listing[], has_more, next_cursor }

Generate listing copyPOST /v1/listings

Write a listing draft for a product: title (≤140 chars), 13 tags (≤20 chars each) and description from Radar keywords and your brand profile, with a trademark check. Creates a draft only; nothing goes live until publish_listing.

listings:writeMCP: generate_listingIdempotency-Key required

Body

product_id *stringID, prefixed prd_.
channel *"etsy" | "shopify"Sales channel.
keywordsstring ≤60[]Target search phrases. Defaults to Radar keywords for the template niche.
nichestring ≤120Buyer niche or occasion, e.g. "nurse graduation gift".
tone"warm" | "playful" | "minimal" | "premium" Default "warm".

Returns 201 Listing

Get a listingGET /v1/listings/{listing_id}

One listing with copy, price, trademark check and channel URL once published.

listings:readMCP: get_listing

Parameters

listing_id *stringpath. ID, prefixed lst_.

Returns 200 Listing

Edit a listing draftPATCH /v1/listings/{listing_id}

Change title, tags, description or price of a draft. The trademark check re-runs on every edit.

listings:writeMCP: update_listingIdempotency-Key required

Parameters

listing_id *stringpath. ID, prefixed lst_.

Body

titlestring ≤140
tagsstring ≤20[]
descriptionstring ≤10000
price_centsinteger 100–1000000

Returns 200 Listing

Publish a listingPOST /v1/listings/{listing_id}/publish

Publish a draft to Etsy or Shopify with personalization fields mapped. Public, and Etsy charges its listing fee. Refused while the trademark check fails. Async: status goes publishing → published (listing.published webhook).

listings:publishMCP: publish_listingIdempotency-Key requiredconfirm: publishesasync (202)

Parameters

listing_id *stringpath. ID, prefixed lst_.

Body

quantityinteger 1–999 Default 999.
as_draft_on_channelbooleanCreate it on Etsy/Shopify as an inactive draft instead of live. Default false.

Returns 202 Listing

Orders

List ordersGET /v1/orders

Orders newest first. status=needs_review is the review queue; each order carries its pre-flight result (clean, warnings, blocked).

orders:readMCP: list_orders

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
status"needs_review" | "approved" | "in_production" | "shipped" | "rejected" | "cancelled"query.
channel"etsy" | "shopify"query. Sales channel.

Returns 200 { data: Order[], has_more, next_cursor }

Get an orderGET /v1/orders/{order_id}

One order with buyer inputs, pinned template versions, pre-flight issues, supplier and proof status.

orders:readMCP: get_order

Parameters

order_id *stringpath. ID, prefixed ord_.

Returns 200 Order

Approve an orderPOST /v1/orders/{order_id}/approve

Approve an order in the review queue: the print file renders and the order is sent to the supplier, which charges your supplier account. Orders with warnings need override_warnings=true; blocked orders can't be approved.

orders:writeMCP: approve_orderIdempotency-Key requiredconfirm: spends money

Parameters

order_id *stringpath. ID, prefixed ord_.

Body

supplier"printify" | "printful" | "gelato" | "own" | "auto"auto lets the router choose (cheapest / fastest / EU-local per your rules). Default "auto".
override_warningsbooleanApprove even when pre-flight shows warnings. Blocked orders can't be approved. Default false.

Returns 200 Order

Batch-approve ordersPOST /v1/orders/approve

Approve up to 100 orders at once. Orders with warnings or blocks are skipped (with the reason), never force-approved.

orders:writeMCP: approve_ordersIdempotency-Key requiredconfirm: spends money

Body

order_ids *string[]
supplier"printify" | "printful" | "gelato" | "own" | "auto" Default "auto".

Returns 200 BatchApproveResult

Reject an orderPOST /v1/orders/{order_id}/reject

Take an order out of production. With refund=true it is also cancelled and refunded on the channel; that can't be undone.

orders:writeMCP: reject_orderIdempotency-Key requiredconfirm: destructive

Parameters

order_id *stringpath. ID, prefixed ord_.

Body

reason *string ≤500Shown in the audit log, and to the buyer if notify_buyer is true.
notify_buyerboolean Default false.
refundbooleanAlso cancel and refund on Etsy/Shopify. Default false.

Returns 200 Order

Send a proof to the buyerPOST /v1/orders/{order_id}/proof

Email / Etsy-message the buyer a proof of the exact print before production. The order waits for their approval.

orders:writeMCP: send_order_proofIdempotency-Key requiredconfirm: contacts buyer

Parameters

order_id *stringpath. ID, prefixed ord_.

Body

messagestring ≤1000Note to the buyer sent with the proof.

Returns 200 Order

Radar

List Radar cardsGET /v1/radar/cards

This week's opportunity cards for the workspace's niches: demand and competition scores (0-100), launch-by date, risk, keywords, suggested product types and a ready-made prompt for create_template.

radar:readMCP: list_radar_cards

Parameters

limitinteger 1–100query. Page size, 1-100. Default 20. Default 20.
cursorstringquery. Opaque cursor from the previous page's next_cursor.
nichestring ≤80query.
risk"low" | "medium" | "high"query.

Returns 200 { data: RadarCard[], has_more, next_cursor }

Get a Radar cardGET /v1/radar/cards/{card_id}

One opportunity card.

radar:readMCP: get_radar_card

Parameters

card_id *stringpath. ID, prefixed rdr_.

Returns 200 RadarCard

Analytics

Sales summaryGET /v1/analytics/summary

Orders, revenue, supplier cost, margin, AOV, views and conversion for a date range (default last 30 days), plus top templates.

analytics:readMCP: get_analytics_summary

Parameters

fromstringquery. YYYY-MM-DD, inclusive. Defaults to 30 days ago.
tostringquery. YYYY-MM-DD, inclusive. Defaults to today.

Returns 200 AnalyticsSummary

Per-template performanceGET /v1/analytics/templates

Views, orders, conversion, revenue and AOV per template, sorted by revenue, orders or conversion.

analytics:readMCP: get_template_analytics

Parameters

fromstringquery. YYYY-MM-DD, inclusive. Defaults to 30 days ago.
tostringquery. YYYY-MM-DD, inclusive. Defaults to today.
sort"revenue" | "orders" | "conversion"query. Default "revenue".
limitinteger 1–100query. Default 20.

Returns 200 { data: TemplateStats[] }

Webhooks

List webhook endpointsGET /v1/webhook-endpoints

Endpoints that receive signed event deliveries. Secrets are never returned after creation.

webhooks:manageMCP: list_webhook_endpoints

Returns 200 { data: WebhookEndpoint[] }

Create a webhook endpointPOST /v1/webhook-endpoints

Subscribe an HTTPS URL to events. The response includes the signing secret once; every delivery carries Pod-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + body)>.

webhooks:manageMCP: create_webhook_endpointIdempotency-Key required

Body

url *stringHTTPS URL that receives POSTed events.
events *"template.created" | "template.version_created" | "render.succeeded" | "render.failed" | "product.created" | "mockups.succeeded" | "listing.published" | "listing.failed" | "order.created" | "order.approved" | "order.rejected" | "order.proof_sent"[]Event types to deliver.
descriptionstring ≤200

Returns 201 WebhookEndpoint

Delete a webhook endpointDELETE /v1/webhook-endpoints/{endpoint_id}

Stop deliveries to an endpoint.

webhooks:manageMCP: delete_webhook_endpointIdempotency-Key requiredconfirm: destructive

Parameters

endpoint_id *stringpath. ID, prefixed whe_.

Returns 200 Deleted

Models

Me

Fields

workspace_id *string
workspace_name *string
plan *"free" | "starter" | "pro" | "scale"
ai_credits_remaining *integer -9007199254740991–9007199254740991
scopes *"workspace:read" | "templates:read" | "templates:write" | "renders:write" | "products:read" | "products:write" | "listings:read" | "listings:write" | "listings:publish" | "orders:read" | "orders:write" | "radar:read" | "analytics:read" | "webhooks:manage"[]
auth *"api_key" | "oauth"
connected_channels *"etsy" | "shopify"[]
connected_suppliers *"printify" | "printful" | "gelato" | "own"[]

PersonalizationField

Fields

key *stringStable key used in render inputs, e.g. name.
label *string ≤80What the buyer sees, e.g. "Pet's name".
type *"text" | "date" | "photo" | "choice" | "color" | "number"
required *boolean Default true.
max_lengthinteger 1–500Text fields only.
optionsstring ≤80[]Choice fields only.

Template

Fields

id *stringID, prefixed tpl_.
object *"template"
name *string
status *"draft" | "generating" | "ready" | "archived"
source *"prompt" | "starter" | "psd" | "blank"
product_types *string ≤64[]
personalization_fields *PersonalizationField[]
current_version *integer -9007199254740991–9007199254740991 | nullLatest immutable version, or null until the first version is cut.
preview_url *string | null
variations *object[]AI artwork variations (prompt templates).
tags *string[]
created_at *stringISO 8601 timestamp, UTC.
updated_at *stringISO 8601 timestamp, UTC.

TemplateVersion

Fields

object *"template_version"
template_id *stringID, prefixed tpl_.
version *integer 1–9007199254740991
note *string | null
doc_hash *stringsha256 of the frozen template document.
created_at *stringISO 8601 timestamp, UTC.

Starter

Fields

id *stringID, prefixed str_.
object *"starter"
name *string
niche *string
product_types *string ≤64[]
preview_url *string
personalization_fields *PersonalizationField[]
created_at *stringISO 8601 timestamp, UTC.

Preview

Fields

object *"preview"
template_id *stringID, prefixed tpl_.
version *integer -9007199254740991–9007199254740991 | null
image_url *stringPNG/SVG data URL or signed URL, valid 1 hour.
width *integer -9007199254740991–9007199254740991
height *integer -9007199254740991–9007199254740991
warnings *object[]Pre-flight warnings: overflow, low-res photo, profanity…

RenderJob

Fields

id *stringID, prefixed rnd_.
object *"render"
status *"queued" | "running" | "succeeded" | "failed"
template_id *stringID, prefixed tpl_.
version *integer -9007199254740991–9007199254740991
product_type *string ≤64Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.
surface *string
format *"png" | "pdf"
file_url *string | nullSigned URL of the print file once succeeded.
dpi *integer -9007199254740991–9007199254740991 | null
content_hash *string | nullContent address; identical inputs never render twice.
warnings *object[]
error *string | null
created_at *stringISO 8601 timestamp, UTC.
completed_at *string | null

CatalogItem

Fields

product_type *string ≤64Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.
name *string
suppliers *object[]
surfaces *object[]

Product

Fields

id *stringID, prefixed prd_.
object *"product"
template_id *stringID, prefixed tpl_.
template_version *integer -9007199254740991–9007199254740991 | null
product_type *string ≤64Catalog product type, e.g. tee, mug-11oz, ornament-round, tumbler-20oz. See list_catalog.
supplier *"printify" | "printful" | "gelato" | "own"Print partner. own = the seller prints in-house.
title *string
retail_price_cents *integer -9007199254740991–9007199254740991
base_cost_cents *integer -9007199254740991–9007199254740991
variants *object[]
fit *object[]
mockup_urls *string[]
created_at *stringISO 8601 timestamp, UTC.

MockupJob

Fields

id *stringID, prefixed mkj_.
object *"mockup_job"
product_id *stringID, prefixed prd_.
status *"queued" | "running" | "succeeded" | "failed"
images *object[]
video_url *string | null
created_at *stringISO 8601 timestamp, UTC.

Listing

Fields

id *stringID, prefixed lst_.
object *"listing"
product_id *stringID, prefixed prd_.
channel *"etsy" | "shopify"Sales channel.
status *"draft" | "publishing" | "published" | "failed"
title *string ≤140
tags *string ≤20[]
description *string
price_cents *integer -9007199254740991–9007199254740991
trademark_check *object
external_id *string | null
url *string | null
created_at *stringISO 8601 timestamp, UTC.
published_at *string | null

Order

Fields

id *stringID, prefixed ord_.
object *"order"
channel *"etsy" | "shopify"Sales channel.
external_id *string
status *"needs_review" | "approved" | "in_production" | "shipped" | "rejected" | "cancelled"
buyer_name *string
ship_country *string ≤2
total_cents *integer -9007199254740991–9007199254740991
currency *string ≤3
items *object[]
preflight *object
supplier *"printify" | "printful" | "gelato" | "own" | null
supplier_cost_cents *integer -9007199254740991–9007199254740991 | null
proof *object | null
rejection_reason *string | null
created_at *stringISO 8601 timestamp, UTC.
updated_at *stringISO 8601 timestamp, UTC.

BatchApproveResult

Fields

approved *string[]
skipped *object[]
total_supplier_cost_cents *integer -9007199254740991–9007199254740991

RadarCard

Fields

id *stringID, prefixed rdr_.
object *"radar_card"
title *string
niche *string
demand_score *number 0–100
competition_score *number 0–100
launch_by *stringDate (YYYY-MM-DD) to launch by to catch the demand curve.
risk *"low" | "medium" | "high"
risk_notes *string[]
keywords *string[]
suggested_product_types *string ≤64[]
suggested_prompt *stringA ready-made brief for create_template (source.type=prompt).
created_at *stringISO 8601 timestamp, UTC.

AnalyticsSummary

Fields

object *"analytics_summary"
from *string
to *string
orders *integer -9007199254740991–9007199254740991
revenue_cents *integer -9007199254740991–9007199254740991
supplier_cost_cents *integer -9007199254740991–9007199254740991
gross_margin_cents *integer -9007199254740991–9007199254740991
aov_cents *integer -9007199254740991–9007199254740991
views *integer -9007199254740991–9007199254740991
conversion_rate *numberOrders / listing views, 0-1.
top_templates *object[]

TemplateStats

Fields

template_id *stringID, prefixed tpl_.
name *string
views *integer -9007199254740991–9007199254740991
orders *integer -9007199254740991–9007199254740991
conversion_rate *number
revenue_cents *integer -9007199254740991–9007199254740991
aov_cents *integer -9007199254740991–9007199254740991

WebhookEndpoint

Fields

id *stringID, prefixed whe_.
object *"webhook_endpoint"
url *string
events *"template.created" | "template.version_created" | "render.succeeded" | "render.failed" | "product.created" | "mockups.succeeded" | "listing.published" | "listing.failed" | "order.created" | "order.approved" | "order.rejected" | "order.proof_sent"[]
description *string | null
enabled *boolean
secretstringSigning secret (whsec_…). Returned only when the endpoint is created.
created_at *stringISO 8601 timestamp, UTC.

WebhookEvent

Fields

id *stringEvent id, evt_…. Use it to de-duplicate deliveries.
object *"event"
type *"template.created" | "template.version_created" | "render.succeeded" | "render.failed" | "product.created" | "mockups.succeeded" | "listing.published" | "listing.failed" | "order.created" | "order.approved" | "order.rejected" | "order.proof_sent"
workspace_id *string
created_at *stringISO 8601 timestamp, UTC.
data *objectThe resource the event is about, in its API shape.

Deleted

Fields

id *string
object *string
deleted *true