- 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.
- The request body is JSONL. Each line is one JSON object carrying one
operation,
upsertordelete. - 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:
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.
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.
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") andtax_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 optionalsluganddescription, and an optionalparent_external_idfor 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 plusproduct_external_id and product_group_external_id to
address its place in the tree:
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 adelete 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.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
priceand, 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_quantityper 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, send1and0. - Content is per locale, on the variant.
locale_contentcarriestitle,description,short_description,slugand 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
bluehere and “Blå” on a Swedish storefront. - Media lives on the product.
media_external_idson 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:
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.

