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.
Most integrations start with a product feed that Depict polls and parses. The Push Product API turns that around. You send products to us the moment they change, in the model our ingestion already speaks, and every line you send comes back with its own result.
  • No polling lag. Your changes reach Depict when you send them, not on the next feed poll.
  • Faster integrations, without waiting on us. Because you send the model we ingest natively, there is no per-merchant transform for Depict to build and maintain, and nothing for you to queue behind. The client is small enough that your team, or a coding agent working from this page, can write it.
  • Errors are loud, not silent. The endpoint validates every line on receipt, and every rejection names the field, the reason and the line. Feeds fail silently; this API refuses to.
  • One contract for everything. The same request shape covers a single price change, your catalog configuration, and a full catalog sync.
Everything goes through one endpoint:
  • The request body is JSONL. Each line is one JSON object carrying one operation, upsert or delete.
  • The response is also JSONL, one line per input line, in the same order. Line N of the response is the receipt for line N of the request. There are no job ids and no status endpoint to poll.
  • The endpoint processes lines in order and applies valid lines even when other lines fail. A request is never all-or-nothing.

Authentication

Every request carries a per-merchant API 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.

Quick example

A pricelist, a warehouse, a market, a category, one product, and one removed size, sent as JSON Lines:
catalog.jsonl:
The response is one line per input line, in the same order:
A rejected line names the field and the reason, and the other lines still apply:
The API reference has the full per-line response contract, including every code and skipped value.

Let your agent build the integration

This page and the endpoint reference are a complete spec, written for an agent to read directly. Point yours at them and let it write the client against your own data source:
This API is still in beta. Review what your agent produces against these pages before going to production, and talk to us so we can flag anything that has moved.

Test your integration

Add ?dry_run=true to any request and the endpoint validates every line and returns the full per-line receipts without changing anything. It writes nothing, starts no processing, and marks the response with an X-Push-Dry-Run: true header. Dry-run your whole catalog before the first real push, and again whenever your client changes.
Combined with mode=full_sync, a dry run is a pre-flight for the sweep. The X-Push-Full-Sync-Would-Sweep response header tells you how many products a real full sync would delete, without deleting anything. One caveat: the endpoint evaluates receipts against the currently stored state. success: true means the line passes validation now, and outcomes like unchanged compare against what is stored at dry-run time. The API reference has the details.

Line types

Every line is an envelope holding three fields: operation, type, and the object itself.
Validation is strict. The endpoint rejects unknown fields instead of ignoring them. A typo’d optional field fails the line loudly instead of silently dropping data. That is the point of this API.
The API also serves the complete line contract as a machine-readable JSON Schema. Use it to generate types or to validate lines before sending. Passing the schema is necessary, not sufficient. The API alone enforces a few rules, such as id character rules, duplicates, and references between lines, so the per-line receipts remain the authority.

Catalog configuration

Prices reference pricelists and stock references warehouses. Those entities must exist before the first product that references them, or earlier in the same request. Configuration lines at the top of the file and products after them works in a single request.
  • Pricelist. Carries the currency (ISO 4217, uppercase, like "SEK") and tax_included. Prices are per pricelist, not per market.
  • Warehouse. A stock location with an optional display name.
  • Market. Points at exactly one pricelist and one or more warehouses. Warehouse order is priority order. Depict shows a product group on a market when at least one of its variants has a price on that market’s pricelist.
  • Category. Carries a per-locale name (required), an optional slug and description, and an optional parent_external_id for the tree.
Pricelist and market ids must not contain :. They become part of field names in Depict’s search index, where a colon has meaning. Use ids like sek or b2b-eur, not country:SE:SEK.

Operations

Upsert

Upserts come in two grains: a whole product tree, or a single variant. A product upsert replaces the product’s whole tree. It sends one product and everything under it, its product groups and their variants, in a single line. Send the complete tree whenever the product’s structure changes. A product group or variant missing from the tree you send is removed, exactly as if you had deleted it. Products you don’t send are untouched, outside full sync. A variant upsert patches exactly one variant. The line carries one whole variant object plus product_external_id and product_group_external_id to address its place in the tree:
It replaces the variant with that external_id in the addressed group, or appends it as a new variant. The unit is the whole variant object. There is no field-level patching, mirroring the replace-tree rule one level down. The endpoint never creates parents implicitly. Addressing a product or group that does not exist fails the line instead of conjuring an empty parent. Resending an identical variant returns "skipped": "unchanged". Use product upserts when structure changes or when your integration has the whole tree at hand. Use variant upserts for high-frequency per-SKU changes, such as a price drop or a stock tick, without resending untouched siblings. source_updated_at (below) lives at product grain, so last-write-wins ordering applies to product upserts only. Every level is identified by external_id, your own identifier, and each one only has to be unique inside its parent. A variant’s external_id needs to be unique within its product group, not across your catalog. Depict derives its internal ids from yours, so sending the same tree again is harmless. The second push updates in place rather than creating anything new, and an identical tree returns "skipped": "unchanged" and costs nothing downstream. That also means ids are contractual. Changing an external_id is a delete plus a create, not a rename, so keep ids stable. Send source_updated_at with each product and Depict applies last-write-wins. A pushed product older than the stored one returns "skipped": "stale_source_updated_at" and is not applied, which makes replays and out-of-order delivery safe.

Delete

Deletes are explicit, and you can address any of the three levels. Send a delete line naming the product, product group or variant, identified by its external_id and those of its parents. Deletes are idempotent. Deleting something already gone succeeds with "skipped": "already_absent".

Full sync

For a full catalog replacement, opt in per request with the query parameter ?mode=full_sync. The endpoint then treats the request as the complete catalog and removes any product not present in it. It never sweeps pricelists, warehouses, markets or categories. A catalog export naturally omits them, and removing them would break everything that references them.
Full sync is the recommended mode for feed-style integrations. If you already produce a periodic full catalog export, send it as one full_sync request on the same schedule. Absent-means-deleted is exactly the semantics a feed has, with per-line receipts instead of silent parsing. Use incremental mode when you push changes as they happen.
Guardrails stop a truncated export from wiping the catalog. The endpoint withholds the sweep if any line in the request failed, or if the request contains no successful product upserts at all. A cut-off file almost always ends in a broken half-line, so truncation trips the first check. It also withholds a sweep that would remove more than half of your live catalog, unless you confirm the intent with allow_mass_delete=true. Your upserts still apply. Only the absent-means-deleted step is skipped, and the response says so in the X-Push-Full-Sync-Sweep-Skipped header. When the sweep runs, X-Push-Full-Sync-Swept carries the number of products removed. To preview a sweep before running it, see Test your integration.

Product format

The format is the model Depict ingests natively, with the same three levels and the same field names our own integrations write. Every catalog has all three levels, and what each one means depends on your vertical. For apparel a product is the style, a product group is the colour and a variant is the size. For furniture the group might be the size the shopper picks first and the variant the colour. One product, in full:
  • Prices are per pricelist, not per market. A pricelist carries the currency and whether prices include tax; your markets point at pricelists. Send price and, when there is one, compare_at_price, the strikethrough price. Depict derives on-sale flags and discounts from the two.
  • Stock is per warehouse. Send sellable_quantity per warehouse and Depict works out per-market availability from the warehouses that serve each market. 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.
  • Content is per locale, on the variant. locale_content carries title, description, short_description, slug and the SEO fields. There is no product-level title. The product’s name lives on every variant, which is also where translations differ.
  • Attributes are keys and values on the variant. Both are locale-invariant strings. Their display labels come from your attribute configuration, so colour can be blue here and “Blå” on a Swedish storefront.
  • Media lives on the product. media_external_ids on a group or variant selects which of the product’s media it shows. Omit it and the group or variant shows all of the product’s media.
  • Order is meaningful. Depict stores product groups, variants and media sources in the order you send them.
  • Depict computes the rest. Internal ids, category paths, availability rollups such as low stock and all-sizes-out-of-stock, discounts and on-sale flags are all derived. You never send them.

Locale codes

Locale codes are lowercase, with an underscore before an optional region: sv, en, sv_se. The endpoint rejects sv-SE and sv_SE rather than normalizing them behind your back. Depict also stores codes verbatim. sv and sv_se are two different locales, each with its own search index. They do not merge, and content pushed under one is invisible under the other. Pick one form per language and use it consistently:
This product is now in two Swedish locales, which is rarely what you want unless you deliberately run sv_se and sv_fi as separate storefronts.

Limits

Both integration shapes run on the same contract: many small requests as products change through the day, or a few large ones for a full sync.

Endpoint reference

Every field, every limit, and the per-line response contract for POST /push/v1/{merchant}/ingest.