Skip to main content
Beta — in development. This API is being finalized. The shapes on this page match the current implementation, but details can still change before general availability. Talk to your Depict contact before building against it. The integration guide covers the concepts: the three-level model, operations, and full sync.

POST /push/v1/{merchant}/ingest

merchant is your Depict merchant id. The request body is JSONL. Each line is one JSON object carrying one operation. The endpoint processes lines in order and responds with JSONL too, one line per input line, in the same order.

Authentication

A missing or wrong key returns 401. The response does not distinguish an unknown merchant from a wrong key.
Your Depict contact issues this key during the beta. It is specific to this API, not the credentials Depict’s storefront or portal APIs use. It authenticates a server-to-server write API, so keep it secret and send it only from your backend, never from a browser.

Query parameters

Parameters parse strictly. Any value other than the ones listed returns 400 rather than defaulting to off, so a misspelled preview flag can never run a real ingest.

Request-level errors

Request-level failures return a JSON error instead of receipts: 401, 400 and 405 refuse the request before any line is processed. The body caps can also trip mid-stream, because the endpoint processes lines as they arrive. Earlier lines are then already applied, and the 413 body names how many lines were processed. The endpoint reports everything else per line, with HTTP status 200. An invalid line gets a failure receipt and processing continues with the next line.

Dry run

Add ?dry_run=true to validate a request without changing anything. The endpoint runs the same per-line validation, returns the same receipt contract, and marks the response with an X-Push-Dry-Run: true header. It writes nothing and starts no processing. Authentication is still required. With mode=full_sync, a dry run also previews the sweep. The response carries X-Push-Full-Sync-Would-Sweep with the number of products a real request would remove, and the guardrail verdicts appear in X-Push-Full-Sync-Sweep-Skipped exactly as they would for real. Nothing is deleted. The endpoint evaluates receipts against the currently stored state, so a dry run is a statement about now, not a reservation:
  • success: true means the line passes validation now. A later real push can still fail on state that changed in between, for example a conflict from concurrent edits.
  • The stateful outcomes unchanged, stale_source_updated_at and already_absent compare against what is stored at dry-run time.
  • A variant line whose parent product exists only earlier in the same request validates against that in-request tree and never reports unchanged, because there is no stored variant to compare with.

Machine-readable schema

The API serves the full JSONL line contract as a JSON Schema (draft 2020-12):
No authentication required. Depict generates the schema from the exact validator the ingest endpoint runs. Its $id names the deployed version it belongs to, and responses carry an ETag for caching. Use it to generate types for your client or to validate lines before sending them.
Passing the schema is necessary, not sufficient. The API enforces some rules that JSON Schema cannot express: no leading or trailing whitespace or control characters in ids, no : in pricelist ids and market names, duplicate-id rejection, and references between lines. The per-line receipts are the authority.

The line envelope

Every line is an object with the same three fields:
"upsert" | "delete"
required
What to do with the object.
string
required
For upsert: product, variant, pricelist, warehouse, market or category. For delete: product, product_group or variant.
object
required
The payload, shaped by type. See below.
Validation is strict at every level. An unknown field anywhere in the object fails the line with validation_error. The API never drops a field silently.

External ids

Every object is identified by external_id, your identifier, 1–255 characters, with no leading or trailing whitespace and no control characters. An id only has to be unique within its parent. Ids are contractual. Depict derives its internal ids from yours, so changing an external_id is a delete plus a create. Pricelist ids, market ids and market names additionally must not contain :, because they become part of field names in Depict’s search index.

References between lines

A product’s pricelist_external_id, warehouse_external_id and category_ids references, and a market’s pricelist and warehouse references, must resolve to an entity that already exists, pushed in an earlier request or on an earlier line of the same request. Put configuration lines before the products that use them. An unknown reference fails the line with validation_error and an error naming the exact path.

Upsert: product

{"operation":"upsert","type":"product","object":{...}} carries the whole product tree. An upsert replaces the stored tree. The endpoint removes product groups and variants absent from the pushed tree.
string
required
Product id, unique across your catalog.
"active" | "draft" | "archived"
default:"active"
Only active products are shown.
string
Product type, for example T-shirts. 1–255 characters.
string
Brand name. 1–255 characters.
string[]
default:"[]"
Up to 250 tags, each 1–255 characters.
string
ISO 8601 timestamp with timezone (2026-08-26T09:12:00Z or with a numeric offset). When present, Depict applies last-write-wins. A pushed product older than the stored one is skipped with stale_source_updated_at, which makes replays and out-of-order delivery safe.
Media[]
default:"[]"
Up to 250 media objects, described under Media. external_ids must be unique within the product.
ProductGroup[]
required
1–500 product groups, in display order. external_ids must be unique within the product.

Product group

string
required
Group id, unique within the product.
"active" | "draft" | "archived"
default:"active"
string[]
default:"[]"
Up to 100 category external_ids. Each must reference a pushed category.
Variant[]
required
1–1000 variants, in display order. external_ids must be unique within the group.
string[]
Which of the product’s media this group shows, by media external_id, up to 100. Omitted means all of the product’s media. Ids not present in the product’s media fail the line.

Variant

string
required
Variant id, unique within the product group.
string
1–255 characters.
string
Display size, for example M. 1–255 characters.
string
1–64 characters.
boolean
default:"true"
Inactive variants are kept but not sellable.
Price[]
default:"[]"
Up to 100 prices, at most one per pricelist:
  • pricelist_external_id (string, required). Must reference a pushed pricelist.
  • price (number, required). The current price, ≥ 0, in the pricelist’s currency.
  • compare_at_price (number). The strikethrough price, ≥ 0. Depict derives on-sale flags and discounts from the two.
InventoryLevel[]
default:"[]"
Up to 100 levels, at most one per warehouse:
  • warehouse_external_id (string, required). Must reference a pushed warehouse.
  • sellable_quantity (integer, required). Must be ≥ 0. Oversold stock is yours to clamp. The endpoint rejects negative values rather than folding them to zero. Prefer real quantities over booleans, since they power signals like low stock. If your source only knows in or out of stock, send 1 and 0.
{ [locale]: LocaleContent }
default:"{}"
Keyed by locale code, lowercase with an underscore before an optional region: sv, en, sv_se. Depict stores codes verbatim, so sv and sv_se are two different locales that never merge. Each value can carry:
  • title (string, 1–4096)
  • description (string, ≤ 65536)
  • short_description (string, ≤ 65536)
  • slug (string, 1–2048)
  • seo_title (string, ≤ 4096)
  • seo_description (string, ≤ 65536)
There is no product-level title. The product’s name lives here, on every variant.
{ [key]: string[] }
default:"{}"
Locale-invariant keys (1–255 characters) mapping to 1–100 values (each 1–8192 characters). Display labels come from your attribute configuration.
string[]
Which of the product’s media this variant shows, up to 100. Omitted means all of the product’s media.

Media

string
required
Media id, unique within the product.
"image" | "video" | "external_video" | "model_3d"
required
string
≤ 4096 characters.
string
Poster image URL for video media. ≤ 4096 characters.
MediaSource[]
required
1–20 sources, renditions of the same media, in order:
  • url (string, required). ≤ 4096 characters.
  • format (string). For example jpg.
  • mime_type (string). For example image/jpeg.
  • width, height (positive integers).

Upsert: variant

{"operation":"upsert","type":"variant","object":{...}} patches exactly one variant into the stored product tree without resending the tree. The object is a full variant plus two addressing fields:
string
required
The variant’s product. It must already exist. An unknown product fails the line with validation_error. The endpoint never creates parents implicitly.
string
required
The variant’s product group under that product. It must already exist.
Semantics:
  • If the addressed group has a variant with this external_id, the pushed variant replaces it. Otherwise the endpoint appends the pushed variant to the group. The unit is the whole variant object. There is no field-level patching.
  • The endpoint validates media_external_ids against the stored product’s media.
  • A variant identical to what is stored returns "skipped": "unchanged".
  • The product’s source_updated_at is untouched. Last-write-wins ordering applies to whole-product upserts only.
  • If concurrent pushes edit the same product while the patch applies, the line fails with conflict. It is safe to retry.

Upsert: catalog configuration

These entities must exist before the first product or market that references them, or earlier in the same request.

Pricelist

{"operation":"upsert","type":"pricelist","object":{...}}
string
required
Must not contain :.
string
required
ISO 4217 code, uppercase, like "SEK".
boolean
required
Whether prices on this pricelist include tax.

Warehouse

{"operation":"upsert","type":"warehouse","object":{...}}
string
required
string
Display name. 1–1024 characters.

Market

{"operation":"upsert","type":"market","object":{...}}
string
required
Must not contain :.
string
required
Display name, 1–1024 characters. Must not contain :.
string
required
The one pricelist serving this market. Must reference a pushed pricelist.
string[]
required
1–100 warehouse ids, in priority order. Earlier warehouses win when computing availability. Each must reference a pushed warehouse.
Depict shows a product group on a market when at least one of its variants has a price on the market’s pricelist.

Category

{"operation":"upsert","type":"category","object":{...}}
string
required
string
The parent category, for building the tree.
{ [locale]: CategoryLocaleContent }
default:"{}"
Keyed by locale code, same rules as variant locale content. Each value:
  • name (string, 1–1024, required)
  • slug (string, 1–2048)
  • description (string, ≤ 65536)

Delete

Deletes address any of the three levels by the external_id path from the product down. Deletes are explicit and idempotent. Omission from an upsert of a different product never deletes anything, and deleting something already gone succeeds with "skipped": "already_absent".

Per-line response

The response has Content-Type: application/x-ndjson, and line N answers the Nth non-empty line of the request. Blank lines carry no receipt. The response is the receipt. There are no job ids and no status endpoint.
boolean
required
Whether the line was accepted.
string
Machine-readable failure class. Present only when success is false:
  • invalid_json. The line is not valid JSON.
  • validation_error. The line failed schema validation or references an unknown pricelist, warehouse or category.
  • conflict. Concurrent pushes edited the same product while the endpoint applied this line’s variant upsert or sub-product delete. Safe to retry.
  • internal_error. Something failed on Depict’s side. Retry, and tell us if it persists.
string
Human-readable path and reason. Present only when success is false, for example product_groups[0].variants[0].prices[0]: unknown pricelist_external_id "nok".
string
Present when the line was accepted but changed nothing:
  • unchanged. The payload is identical to what is stored.
  • stale_source_updated_at. The stored product has a newer source_updated_at, so last-write-wins kept it.
  • already_absent. A delete of something already gone.
Depict may add response fields over time. Ignore fields you do not recognise.
Receipts currently arrive after the endpoint has processed the whole request, not streamed line by line. Your client can still stream the upload and read the full response at the end.

Full sync response headers

With ?mode=full_sync, response headers report the outcome of the absent-means-deleted sweep: