{"openapi":"3.1.0","info":{"title":"AdCore public API","version":"1.0.0","description":"Read access to your AdCore catalogue (products, variants and DAM assets) and the events that change it.\n\n**Authentication.** Send `Authorization: Bearer adc_<id>.<secret>`. A key belongs to one store (tenant) and reads only that store; anything of another store answers 404. A store admin issues, rotates and revokes keys in Settings → API, where each new key is shown once, together with its usage and refusals.\n\n**Key lifecycle.** A key is valid for 90 days from issue. Rotation hands you a new key while the previous one keeps working for 7 more days (never past its own end), so you can switch without downtime; after that the previous key answers `401`. Every successful response carries the key's end in `X-AdCore-Key-Expires-At`, and in its last 14 days the days left in `X-AdCore-Key-Expires-In-Days`. A revoked key answers `401` at once. Every authentication failure (missing, malformed, unknown, expired, revoked or otherwise invalid key, a wrong secret included, or a key of an inactive store) is the same `401`: the reason is recorded on our side, never in the response. A valid, live key that may not perform the operation answers `403` instead: `insufficient_scope` when it has the other scope (read or write), with the required scope in `WWW-Authenticate`, `write_disabled` when it is a write key and write access is not enabled for your store, and `not_contracted` when the product the operation serves is not in your store's contract. Only a key whose secret is correct can receive a `403`.\n\n**Limits.** 120 requests per 60 seconds per key, reported in `X-RateLimit-*`; and 600 requests per 60 seconds per source IP, counted before the key is checked. Either one answers `429` with `Retry-After`. Pages hold at most 100 items.\n\n**Events.** `product.published` and `asset.changed` are read from `GET /api/v1/events` with a cursor. An event is served once it is 120 seconds old, so a write that commits late is not skipped by a cursor that already moved on. Each event `id` is stable: use it to de-duplicate.\n\n**Webhooks.** The same events can be pushed, signed, to an `https` URL of yours (see `webhooks`). Delivery normally starts within an hour of AdCore registering the URL, once delivery is enabled for your store; if delivery for your store stops on the AdCore side, it resumes after AdCore restarts its delivery worker. Before delivery starts, while it is stopped, and at any time, the pull feed above serves the same events with the same `id`."},"servers":[{"url":"https://www.adcore.com.br"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/products":{"get":{"operationId":"listProducts","summary":"List products","description":"Ordered by the product record's own last update (`updatedAt`), newest first. Editing only an attribute value or a variant does not move a product in this order.","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":2147483647,"default":0}}],"responses":{"200":{"description":"A page of products.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProductSummary"}},"pagination":{"type":"object","required":["limit","offset","total"],"properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"400":{"description":"Invalid `limit` or `offset`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/products/{productId}":{"get":{"operationId":"getProduct","summary":"Get a product with its variants and assets","parameters":[{"name":"productId","in":"path","required":true,"description":"Product id.","schema":{"type":"string","maxLength":64}}],"responses":{"200":{"description":"The product.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Product"}}}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such product in your store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/variants":{"get":{"operationId":"listVariantsBySku","summary":"Get variants by SKU","description":"Up to 100 SKUs per call, repeating the parameter: `?sku=SKU-1001&sku=SKU-1002`. A SKU matches the SKU your catalogue shows for the variant: letter case is ignored (send each `sku` without surrounding spaces). A requested SKU containing `ß` finds a stored code containing `ß` or `SS`, but a stored code containing `ß` is not guaranteed to match a request containing `SS`. The placeholder variant a product with several variants carries under the product code is not returned. When your store has a SKU code rule that names the product field holding the SKU of a single-variant product (whose only variant carries the product code), that product is found by the SKU in that field; without such a rule, by its variant code. Each SKU with no variant in your store comes back in `notFound`, spelled as you sent it; a SKU sent twice is answered once. A SKU that matches more than one variant (two codes that differ only in case, for example) returns each of them, all with the same `requestedSku`: key your results by `requestedSku` and expect a list. Each variant carries at most 20 assets (`assetsTotal` says how many it has), and a response that would exceed 2097152 bytes answers 400: send fewer SKUs per call.","parameters":[{"name":"sku","in":"query","required":true,"description":"Repeat to look up several SKUs, 1 to 100.","style":"form","explode":true,"schema":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[^\\s,\\u0000-\\u001f\\u007f-\\u009f](?:[^,\\u0000-\\u001f\\u007f-\\u009f]*[^\\s,\\u0000-\\u001f\\u007f-\\u009f])?$"}},"example":["sku-1001","SKU-9999"]}],"responses":{"200":{"description":"The variants found, in the order of the SKUs sent, and the SKUs not found.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","notFound"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VariantBySku"}},"notFound":{"type":"array","items":{"type":"string"}}}},"example":{"data":[{"id":"cm0examplevariant0001","productId":"cm0exampleproduct0001","sku":"SKU-1001","requestedSku":"sku-1001","name":"Example product 110V","isDefault":false,"assets":[{"id":"cm0exampleasset000001","variantId":"cm0examplevariant0001","url":"https://www.adcore.com.br/api/media/example-token?v=3","altText":"Front view","tags":["front"],"isPrimary":true,"format":"jpg","kind":"image","videoLink":null,"documentRole":null,"width":1200,"height":1200,"sizeBytes":245760,"version":3,"approvalStatus":"APPROVED","usageRights":{"license":null,"expiresAt":null},"createdAt":"2026-10-01T12:00:00.000Z","updatedAt":"2026-10-20T09:30:00.000Z"}],"assetsTotal":1}],"notFound":["SKU-9999"]}}}},"400":{"description":"No `sku`, more than 100, a `sku` out of shape (empty, longer than 100 characters, with a comma, a control character or surrounding space), or a response above 2097152 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/variants/{variantId}":{"get":{"operationId":"getVariant","summary":"Get a variant (SKU) with its assets","parameters":[{"name":"variantId","in":"path","required":true,"description":"Variant id.","schema":{"type":"string","maxLength":64}}],"responses":{"200":{"description":"The variant.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/VariantWithAssets"}}}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such variant in your store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/assets/{assetId}":{"get":{"operationId":"getAsset","summary":"Get a DAM asset","parameters":[{"name":"assetId","in":"path","required":true,"description":"Asset id.","schema":{"type":"string","maxLength":64}}],"responses":{"200":{"description":"The asset.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Asset"}}}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such asset in your store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/events":{"get":{"operationId":"listEvents","summary":"Pull events after a cursor","description":"Oldest first. Pass `nextCursor` back as `cursor` to resume exactly after the last event received; it stays the same when nothing new arrived. Both event types come from recorded history, so a change that was not recorded emits nothing; the `ProductPublishedEvent` and `AssetChangedEvent` schemas say which.","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Repeat to select several types.","style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","enum":["product.published","asset.changed"]}}}],"responses":{"200":{"description":"A page of events.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"nextCursor":{"type":["string","null"]}}}}}},"400":{"description":"Invalid `limit`, `cursor` or `type`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A valid, live key that may not perform this operation. `insufficient_scope`: the key has the other scope (a write key on this read operation, or a read key on a write operation); `WWW-Authenticate` is `Bearer error=\"insufficient_scope\", scope=\"<the scope this operation requires>\"`, so use the key of that scope. `not_contracted`: the product this operation serves is not in your store's contract. `write_disabled` (write operations only): the write key is right but write access is not enabled for your store; an AdCore administrator enables it. Only `insufficient_scope` carries `WWW-Authenticate`, and no `403` carries `X-RateLimit-*`. Do not retry with the same key: the request fails the same way until the key, the contract or the setting changes.","headers":{"WWW-Authenticate":{"description":"Present only with `insufficient_scope`, naming the required scope (`read` or `write`).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/exports/latest":{"get":{"operationId":"getLatestExport","summary":"Get a complete Data Lake snapshot with download links","description":"Requires a read key, PIM in the site contract and Data Lake enabled for the site. Reads completed snapshots only; it never starts generation. Defaults to jsonl (ndjson is an alias); parquet must have been generated separately. With dt, reads the last complete snapshot for that UTC day; without dt, the latest complete snapshot of the format. Retention is 30 UTC days including today. Each file URL expires after 900 seconds. Keep the URLs private. Verify every downloaded file against its size, row count and SHA-256. A disabled export answers 403 datalake_disabled; a missing, incomplete or out-of-retention snapshot answers 404 export_not_ready.","parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["jsonl","ndjson","parquet"],"default":"jsonl"}},{"name":"dt","in":"query","required":false,"description":"UTC partition date within retention.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The complete manifest, with a short-lived URL on each file.","headers":{"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}},"X-AdCore-Key-Expires-At":{"description":"When the key used for this request stops working (ISO 8601).","schema":{"type":"string","format":"date-time"}},"X-AdCore-Key-Expires-In-Days":{"description":"Present only in the last 14 days of the key: whole days left, rounded up. Rotate the key before it reaches zero.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["contract_version","tenant_id","snapshot_id","snapshot_at","partition_date","format","mode","include","previous_snapshot_id","tables","expires_in"],"properties":{"contract_version":{"type":"string","pattern":"^1\\.[0-9]+$"},"tenant_id":{"type":"string"},"snapshot_id":{"type":"string"},"snapshot_at":{"type":"string","format":"date-time"},"partition_date":{"type":"string","format":"date"},"format":{"type":"string","enum":["jsonl","parquet"]},"mode":{"type":"string","const":"full"},"include":{"type":"object","required":["prices"],"properties":{"prices":{"type":"boolean"}}},"previous_snapshot_id":{"type":["string","null"]},"expires_in":{"type":"integer","const":900},"tables":{"type":"array","items":{"type":"object","required":["name","row_count","files"],"properties":{"name":{"type":"string"},"row_count":{"type":"integer","minimum":0},"files":{"type":"array","items":{"type":"object","required":["path","row_count","size_bytes","sha256","url"],"properties":{"path":{"type":"string"},"row_count":{"type":"integer","minimum":0},"size_bytes":{"type":"integer","minimum":0},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"url":{"type":"string","description":"Private download capability, valid for 15 minutes."}}}}}}}}}}}}}},"400":{"description":"Unknown, repeated or invalid query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown, expired, revoked or otherwise invalid API key (a wrong secret included), or a key of an inactive store. Every one of these is the same `401` with `WWW-Authenticate: Bearer` and the code `unauthorized`.","headers":{"WWW-Authenticate":{"schema":{"type":"string","const":"Bearer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"datalake_disabled, not_contracted or insufficient_scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"export_not_ready: no complete snapshot for the requested format/date within retention.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by the per-key or the per-IP bucket. Retry after the number of seconds in `Retry-After`. `X-RateLimit-*` is present only when the KEY bucket is the one spent.","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this key.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) when the window resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This document","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"webhooks":{"product.published":{"post":{"operationId":"onProductPublished","summary":"A product finished publishing to a sales channel","description":"Every delivery is a `POST` with a JSON body (the event, the same object `GET /api/v1/events` returns) and three headers: `X-AdCore-Event` (the event type), `X-AdCore-Event-Id` (the event id; use it to de-duplicate, a delivery can repeat) and `X-AdCore-Signature: t=<unix seconds>,v1=<hex>`, where `v1 = HMAC-SHA256(<your signing secret>, \"<t>.<raw body>\")`. Verify by recomputing `v1` over the RAW body bytes, comparing in constant time, and rejecting a `t` more than 300 seconds from your clock. Answer any 2xx to acknowledge; the response body is not read. 408, 429, 5xx and network failures are retried, waiting 60 seconds and doubling up to 3600 seconds, up to 8 recorded attempts per event; events are delivered in feed order, so a retried event holds the ones after it. An attempt AdCore cannot record is not counted, so while that lasts an event can be POSTed more than 8 times; de-duplicate on `X-AdCore-Event-Id`. Any other 4xx and any 3xx are not retried, and redirects are never followed. Deliveries go only to `https` URLs on public addresses. Delivery to your URL normally starts within an hour of AdCore registering it, once delivery is enabled for your store, and covers events from the registration on (a URL removed and registered again starts over from the new registration). If delivery for your store stops on the AdCore side, it resumes after AdCore restarts its delivery worker; at any time, delivering or not, the same events (same `id`) are available from `GET /api/v1/events`.","parameters":[{"name":"X-AdCore-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=\\d+,v1=[0-9a-f]{64}$"}},{"name":"X-AdCore-Event","in":"header","required":true,"schema":{"type":"string","const":"product.published"}},{"name":"X-AdCore-Event-Id","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductPublishedEvent"}}}},"responses":{"2XX":{"description":"Acknowledged."}}}},"asset.changed":{"post":{"operationId":"onAssetChanged","summary":"A DAM asset was created, replaced, edited, approved or deleted","description":"Every delivery is a `POST` with a JSON body (the event, the same object `GET /api/v1/events` returns) and three headers: `X-AdCore-Event` (the event type), `X-AdCore-Event-Id` (the event id; use it to de-duplicate, a delivery can repeat) and `X-AdCore-Signature: t=<unix seconds>,v1=<hex>`, where `v1 = HMAC-SHA256(<your signing secret>, \"<t>.<raw body>\")`. Verify by recomputing `v1` over the RAW body bytes, comparing in constant time, and rejecting a `t` more than 300 seconds from your clock. Answer any 2xx to acknowledge; the response body is not read. 408, 429, 5xx and network failures are retried, waiting 60 seconds and doubling up to 3600 seconds, up to 8 recorded attempts per event; events are delivered in feed order, so a retried event holds the ones after it. An attempt AdCore cannot record is not counted, so while that lasts an event can be POSTed more than 8 times; de-duplicate on `X-AdCore-Event-Id`. Any other 4xx and any 3xx are not retried, and redirects are never followed. Deliveries go only to `https` URLs on public addresses. Delivery to your URL normally starts within an hour of AdCore registering it, once delivery is enabled for your store, and covers events from the registration on (a URL removed and registered again starts over from the new registration). If delivery for your store stops on the AdCore side, it resumes after AdCore restarts its delivery worker; at any time, delivering or not, the same events (same `id`) are available from `GET /api/v1/events`.","parameters":[{"name":"X-AdCore-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=\\d+,v1=[0-9a-f]{64}$"}},{"name":"X-AdCore-Event","in":"header","required":true,"schema":{"type":"string","const":"asset.changed"}},{"name":"X-AdCore-Event-Id","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetChangedEvent"}}}},"responses":{"2XX":{"description":"Acknowledged."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"adc_<id>.<secret>"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","insufficient_scope","write_disabled","not_contracted","datalake_disabled","export_not_ready","rate_limited","not_found","invalid_request","internal_error"]},"message":{"type":"string"}}}}},"ProductSummary":{"type":"object","required":["id","code","name","status","familyId","sku","attributes"],"properties":{"id":{"type":"string"},"code":{"type":"string","description":"Immutable product code."},"name":{"type":"string"},"status":{"type":"string","enum":["draft","active","archived"]},"familyId":{"type":["string","null"]},"sku":{"type":["string","null"],"description":"The default variant's SKU."},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Non-empty attribute values keyed by attribute code."}}},"Product":{"allOf":[{"$ref":"#/components/schemas/ProductSummary"},{"type":"object","required":["variants","assets"],"properties":{"variants":{"type":"array","items":{"$ref":"#/components/schemas/Variant"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/Asset"}}}}]},"Variant":{"type":"object","required":["id","productId","sku","name","isDefault"],"properties":{"id":{"type":"string"},"productId":{"type":"string"},"sku":{"type":"string","description":"The variant code. Its letter case can differ from the requested SKU; see `requestedSku` on lookup results."},"name":{"type":"string"},"isDefault":{"type":"boolean"}}},"VariantWithAssets":{"allOf":[{"$ref":"#/components/schemas/Variant"},{"type":"object","required":["assets"],"properties":{"assets":{"type":"array","items":{"$ref":"#/components/schemas/Asset"}}}}]},"VariantBySku":{"allOf":[{"$ref":"#/components/schemas/Variant"},{"type":"object","required":["requestedSku","assets","assetsTotal"],"properties":{"requestedSku":{"type":"string","description":"The `sku` you sent that this variant answers, spelled as you sent it. `sku` is the variant code, as in `GET /api/v1/variants/{variantId}`."},"assets":{"type":"array","maxItems":20,"description":"Newest version first, at most 20. Read the rest with `GET /api/v1/variants/{variantId}`.","items":{"$ref":"#/components/schemas/Asset"}},"assetsTotal":{"type":"integer","description":"How many assets the variant has."}}}]},"Asset":{"type":"object","required":["id","variantId","url","altText","tags","isPrimary","format","width","height","sizeBytes","version","approvalStatus","usageRights","createdAt","updatedAt"],"properties":{"id":{"type":"string"},"variantId":{"type":["string","null"]},"url":{"type":["string","null"],"format":"uri","description":"Public URL of the original file, or null when the file cannot be served."},"altText":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"isPrimary":{"type":"boolean"},"format":{"type":"string"},"kind":{"type":"string","enum":["image","video","document","model3d","other"],"description":"Media kind from the DAM classifier, independent of format casing; other for an unclassified format. Original 3D files and internal documents are never listed (lifeos-work#2805).","example":"video"},"videoLink":{"type":["object","null"],"description":"Canonical hosted video link, only for videos that pass the publication gate. Does not require stored bytes.","required":["provider","url"],"properties":{"provider":{"type":"string","enum":["youtube","vimeo"]},"url":{"type":"string","format":"uri"}},"example":{"provider":"vimeo","url":"https://vimeo.com/123456789/abcdef1234"}},"documentRole":{"type":["string","null"],"description":"Stored document role, only for documents.","example":"manual"},"width":{"type":"integer"},"height":{"type":"integer"},"sizeBytes":{"type":"integer"},"version":{"type":"integer"},"approvalStatus":{"type":"string","enum":["PENDING","APPROVED","REJECTED"]},"usageRights":{"type":"object","required":["license","expiresAt"],"properties":{"license":{"type":["string","null"]},"expiresAt":{"type":["string","null"],"format":"date-time"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ProductPublishedEvent":{"type":"object","description":"Emitted when a publication to a sales channel completes and its result is recorded in the publication history. If recording that result fails (for example during a brief database outage), the publication still stands but no event is emitted for it. To reconcile, read the product again.","required":["id","type","occurredAt","data"],"properties":{"id":{"type":"string","description":"Stable event id: the same event always carries the same id, so a repeat can be dropped."},"type":{"const":"product.published"},"occurredAt":{"type":"string","format":"date-time"},"data":{"type":"object","required":["productId","channelId","attempt","publishedAt"],"properties":{"productId":{"type":"string"},"channelId":{"type":"string"},"attempt":{"type":"integer"},"publishedAt":{"type":"string","format":"date-time"}}}}},"AssetChangedEvent":{"type":"object","description":"Emitted for asset changes recorded in the DAM audit trail: actions taken in AdCore (upload, replace, delete, alt text, approval, usage rights, restore, link, tags, main image, collection membership). Only completed changes are emitted: a request to rebuild an asset's renditions is not an event, and an upload that matched an existing file is an event only when it linked that asset to a variant. Automatic background changes are not emitted yet: an asset bound to a variant by a catalogue import, copied between sibling variants, undone by an import rollback, or re-rendered by a media job. To reconcile those, read the product or asset again.","required":["id","type","occurredAt","data"],"properties":{"id":{"type":"string"},"type":{"const":"asset.changed"},"occurredAt":{"type":"string","format":"date-time"},"data":{"type":"object","required":["assetId","change"],"properties":{"assetId":{"type":"string"},"change":{"type":"string","description":"What changed, e.g. `upload`, `replace`, `delete`, `alt-text`, `approval`, `usage-rights`, `restore`, `link`, `tags`."}}}}},"Event":{"oneOf":[{"$ref":"#/components/schemas/ProductPublishedEvent"},{"$ref":"#/components/schemas/AssetChangedEvent"}],"discriminator":{"propertyName":"type","mapping":{"product.published":"#/components/schemas/ProductPublishedEvent","asset.changed":"#/components/schemas/AssetChangedEvent"}}}}}}