Exporting and syncing
Read every product with every asset we hold for it, one row per SKU. Ask for what changed since your last run, or freeze a selection into a file.
The export is the read a PIM or DAM sync wants. It is deliberately not a file list: relations are the payload, so your side never parses filenames to work out which product a render belongs to.
for row in pages: product = upsert_by_reference(row.product) for asset in row.assets: upsert_asset(product, asset.id, asset.url)Read the catalogue
GET/v1/exports
Cursor-paginated products, newest first, each with every asset we hold for it.
{ "product": { "id": "01HX5K2MZ7A3Q4FBNDC0EVDXY1", "reference": "TEE-SAGE-001", "name": "Crewneck Tee — Sage", "sex": "female", "deleted_at": null }, "assets": [ { "id": "01HX5K2MZ7A3Q4FBNDC0EVDXYS", "role": "source", "url": "https://…signed…", "url_expires_at": "2026-09-03T15:32:00Z", "mime_type": "image/jpeg", "width": 2000, "height": 2667, "ai_generated": false, "watermarked": false, "derived": false, "created_at": "2026-09-01T09:12:44Z", "task_id": null, "attributes": { "orientation": "front" }, "also_features": [], "suggested_filename": "TEE-SAGE-001_front.jpg" }, { "id": "01HX5K2MZ7A3Q4FBNDC0EVDXZ9", "role": "tryon", "url": "https://…signed…", "url_expires_at": "2026-09-03T15:32:00Z", "mime_type": "image/webp", "width": 1536, "height": 2048, "ai_generated": true, "watermarked": false, "derived": false, "created_at": "2026-09-02T16:40:03Z", "task_id": "01HX5K2MZ7A3Q4FBNDC0EVDXY2", "attributes": { "angle_shot": "full_body_relaxed_symmetric", "background": "park", "model_id": "01HX5K2MZ7A3Q4FBNDC0EVDXMD1" }, "also_features": ["01HX5K2MZ7A3Q4FBNDC0EVDXPN"], "suggested_filename": "TEE-SAGE-001_tryon_park.webp" } ]}What an asset tells you
| Field | Use it for |
|---|---|
role |
What the asset is: source (your photo), beautifier, tryon, recoloring. Edits keep the role and set derived: true. New roles may appear; treat unknown values as opaque. |
id |
An image id. One expired URL is re-fetched with GET /v1/images/{id}, never by re-running the export. |
url, url_expires_at |
A signed URL to the active version of the image, the one the dashboard shows. Valid for one hour. |
ai_generated |
false only for your own photos. For EU AI Act labelling. |
watermarked |
The bytes carry a watermark. This can change for a stable id when your plan changes. |
task_id |
The task that produced it, for GET /v1/tasks/{id}. Null for your photos and for generations made in the dashboard. |
attributes |
Flat, open facts per role: orientation, angle_shot, background, model_id, edit_number. Ignore keys you do not know. |
also_features |
Other products visible in this asset (tryon stylings). |
suggested_filename |
A filename, never a path, built from your organization’s export naming template with the real extension of the bytes. |
Each asset appears exactly once, under the product it was made for. A tryon of a top styled with pants lives on the top; the pants are listed in also_features. Whether it also goes on the pants page is your merchandising call.
Filters
role=beautifier,tryonlimitsassetsto those roles. Products are still listed when nothing matches; theirassetsis then empty.limit(1 to 100, default 25) andcursorpage through, exactly likeGET /v1/products.
Only what changed
Add since=<timestamp> and the same endpoint returns only the products that changed at or after that instant: a field edited, a photo attached, a generation or edit completed, or the product deleted.
curl "https://api.veeton.com/v1/exports?since=2026-09-02T00:00:00Z" \ -H "Authorization: Bearer $VEETON_KEY"Each returned row is the product’s full current state, so upsert it whole; you never merge partial deltas. A deleted product appears with product.deleted_at set and an empty assets array. Remove it on your side.
Use the created_at of your previous run, minus a safety margin, as the next since. A nightly job is then a delta, not a re-export.
Freeze a selection into a file
POST/v1/exports
The same rows as a stable artifact: a JSONL manifest (one row per line) or a ZIP of the image files rendered by the same export pipeline the dashboard uses, with your organization’s naming template and per-workflow formats.
curl https://api.veeton.com/v1/exports \ -H "Authorization: Bearer $VEETON_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"format":"zip","product_ids":["01HX5K2MZ7A3Q4FBNDC0EVDXY1"],"role":["beautifier","tryon"]}'- Select up to 500 products with
product_ids, or omit it for the whole catalogue visible to the API (same cap). Narrow withrole. format: manifest(default) is written immediately; the response is alreadycompletedwith a signedurl.format: zipis rendered asynchronously. PollGET /v1/exports/{id}, which carriesRetry-Afterwhileprocessing, or subscribe toexport.completedandexport.failed.
{ "id": "01HX5K2MZ7A3Q4FBNDC0EVDXEX", "format": "zip", "status": "processing", "url": null, "url_expires_at": null, "total_assets": 148, "error": null, "created_at": "2026-09-03T14:30:09Z", "finished_at": null}Asset URLs inside a manifest are valid one hour after it was written; the asset ids are permanent. The artifact URL on the job is re-signed on every read, so cache the file, not the URL.