Partner API
Povito Partner API v1.0.0
Sync a Povito seller's catalog, stock, prices and orders with the seller's own ERP, POS or inventory system.
Download contract partner-v1.yaml Try requests in the Sandbox explorer
8 operations, generated at build time from the contract. Keys and base URLs for each environment: Keys and environments.
Base URLs#
| Environment | Base URL |
|---|---|
Sandbox Sandbox — accepts only povito_sk_test_ keys | https://api-staging.povito.com/partner/v1 |
Live — accepts only povito_sk_live_ keys | https://api.povito.com/partner/v1 |
Overview#
Machine-to-machine REST for sellers whose stock lives in their own system. Bulk-first and keyed by the seller's own SKU, so an integration never has to store Povito's internal ids.
Environments#
| Environment | Base URL | Keys accepted |
|---|---|---|
| Sandbox | https://api-staging.povito.com/partner/v1 |
povito_sk_test_… only |
| Live | https://api.povito.com/partner/v1 |
povito_sk_live_… only |
A well-formed key presented to the other environment is refused with
401 api_key_wrong_environment before it is looked up, and the message
names the key's environment. Nothing a Sandbox integration writes is shown
to shoppers.
Keys#
Keys are issued, re-scoped and revoked in the Seller dashboard under Settings → Integrations, never through this API. The full key is shown exactly once, when it is created; Povito stores only a hash of the secret.
Format: povito_sk_{test|live}_{key_id}_{secret}. Both segments can contain
_ and -, so treat the whole key as opaque and never split it on _.
A key belongs to exactly one seller and only ever reads or writes that seller's data. Revocation takes effect on the next request.
Scopes#
catalog:read, catalog:write, inventory:write, orders:read,
webhooks:manage (reserved — no webhook routes exist yet). Each operation
names the one scope it checks; a key without it gets 403 insufficient_scope.
Conventions#
- Money is integer Iraqi dinars. IQD has no minor units:
25000is twenty-five thousand dinars. - Timestamps are ISO 8601 in UTC.
- Pagination is forward-only by opaque cursor: pass
next_cursorback ascursoruntilhas_moreis false. Page onhas_more, not on page size. - Bulk results are per row.
POST /products/bulkandPATCH /inventory/bulkanswer200even when rows fail; readresults[]. A top-level4xxmeans the request itself was refused. Idempotency-Keyis required on all three write routes. The first outcome is stored for 24 hours against the key and the API key that sent it, and a repeat with the same body gets it back withIdempotent-Replayed: true. The same key with a different body or on another route is409 idempotency_key_conflict; a repeat while the first request is still running is409 idempotency_key_in_progress.- Errors use one envelope:
{ "error": { "code", "message", "details"?, "request_id" } }. Quoterequest_idwhen you contact support.
Authentication#
partnerApiKey#
HTTP bearer — Authorization: Bearer …
Authorization: Bearer povito_sk_{test|live}_{key_id}_{secret}.
The scheme word must be exactly Bearer followed by one space; any
other form is treated as no key. No other header is read, and seller
dashboard or shopper tokens are always refused. test keys work only
in Sandbox and live keys only in Live; the other environment answers
401 api_key_wrong_environment.
Endpoints#
Health
Connectivity probe, no key required.
GET /health#
Check connectivity
Public. Runs no auth middleware, so it answers 200 whatever
Authorization header is sent — use it to confirm the host, not the key.
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The Partner API is reachable. | Health |
Examples
curl "https://api-staging.povito.com/partner/v1/health"{
"status": "ok",
"api": "partner",
"version": "v1",
"currency": "IQD"
}Catalog
Read and upsert products keyed by the seller's own SKU.
GET /products#
List the seller's products
Pages of products — every product this seller has an offer on — newest product first (by Povito product id, a stable order).
limitcounts products. Each product carries all of this seller's variants of it, however many, so a product never appears on two pages.statusis applied in the same query that cuts the page, so a page ofdraftproducts holdslimitof them whenever more exist.
Cursors from before this pagination (offer ids, offer_…) are refused
with 400; start again from the first page. There is no
updated_since filter; to catch up, re-pull from the first page.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
status | query | ProductStatus | Keep only products in this status. Unknown values are not rejected; they match nothing. |
cursor | query | string | The next_cursor of the previous page; omit for the first page. Opaque. A value that is not a cursor from this list is refused with 400. |
limit | query | integer | Products per page. A value that is not a positive integer is ignored and the default applies; values above 100 are capped at 100. default 20 · min 1 · max 100 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | One page of products. | ProductPage |
400 | validation_error — cursor is not a cursor from this list, for example an offer-id cursor from before pages were cut by product (details[]: {field: cursor, issue: invalid}). | ErrorEnvelope |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl "https://api-staging.povito.com/partner/v1/products" \
-H "Authorization: Bearer povito_sk_test_..."{
"data": [
{
"id": "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C",
"title": "Sony WH-1000XM5 Wireless Headphones",
"handle": "sony-wh-1000xm5-wireless-headphones",
"status": "published",
"description": "Noise-cancelling over-ear headphones, black.",
"thumbnail": "https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg",
"images": [
"https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg"
],
"category_ids": [
"pcat_01K3W6H8M2P4R6T8V0X2Z4B6D8"
],
"brand": {
"id": "brand_01K3Y2A4C6E8G0J2L4N6Q8S0U2",
"name": "Sony",
"slug": "sony"
},
"variants": [
{
"offer_id": "offer_01K4R8Z3A1B2C3D4E5F6G7H8J9",
"variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8",
"sku": "ERP-1042",
"options": {
"Default": "Default"
},
"price": 425000,
"sale_price": null,
"campaign_label": null,
"stocked_quantity": 12,
"condition": null,
"notes": null
}
],
"catalog_managed": false,
"handling": "none",
"rejection_reason": null,
"created_at": "2026-09-01T10:00:00.000Z",
"updated_at": "2026-09-04T08:12:00.000Z"
}
],
"next_cursor": "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C",
"has_more": true
}GET /products/{sku}#
Get one product by the seller's SKU
Resolves the SKU to one of this seller's variants and returns the whole product with all of its variants. Any variant's SKU returns the same product. If the seller has reused one SKU on several products, the first match is returned.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
skurequired | path | string | The seller's own SKU (product_variant.sku), URL-encoded.min length 1 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The product. | Product |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
404 | product_not_found — the SKU matches none of this seller's products. | ErrorEnvelope |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl "https://api-staging.povito.com/partner/v1/products/{sku}" \
-H "Authorization: Bearer povito_sk_test_..."{
"id": "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C",
"title": "Sony WH-1000XM5 Wireless Headphones",
"handle": "sony-wh-1000xm5-wireless-headphones",
"status": "published",
"description": "Noise-cancelling over-ear headphones, black.",
"thumbnail": "https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg",
"images": [
"https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg"
],
"category_ids": [
"pcat_01K3W6H8M2P4R6T8V0X2Z4B6D8"
],
"brand": {
"id": "brand_01K3Y2A4C6E8G0J2L4N6Q8S0U2",
"name": "Sony",
"slug": "sony"
},
"variants": [
{
"offer_id": "offer_01K4R8Z3A1B2C3D4E5F6G7H8J9",
"variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8",
"sku": "ERP-1042",
"options": {
"Default": "Default"
},
"price": 425000,
"sale_price": 382500,
"campaign_label": "Back to School",
"stocked_quantity": 12,
"condition": null,
"notes": null
}
],
"catalog_managed": false,
"handling": "none",
"rejection_reason": null,
"created_at": "2026-09-01T10:00:00.000Z",
"updated_at": "2026-09-04T08:12:00.000Z"
}POST /products/bulk#
Create or update up to 50 products
Upsert keyed by external_sku. Rows run concurrently and each gets its
own result.
- SKU already on one of this seller's products → update. Only
title,descriptionandcategory_idsare applied;price_iqdandinitial_stockare silently ignored (set price and stock withPATCH /inventory/bulk). Updating apublishedproduct moves it back toproposedfor review. Catalog-managed products refuse title, description and category changes (catalog_product_locked). - New SKU → create.
titleandprice_iqdare required. The product is created asdraftwith a singleDefaultvariant carrying the SKU,initial_stock(default 0) at the seller's first stock location, and the price. This API has no submit-for-review call: drafts stay drafts until submitted from the seller dashboard.
Every row problem is a rejected result, never a 400 for the whole
request — including a row that is not an object or has a missing or
blank external_sku. Such a row's result has external_sku: null;
results are in request order, so its position identifies it. Other
fields of the wrong JSON type are dropped rather than rejected. Do not
repeat a SKU within one request: rows run concurrently, so two creates
for the same new SKU can race.
Idempotent. The first outcome is stored for 24 hours against the
Idempotency-Key and the API key that sent it. A repeat with the same
body gets the stored status and body back with Idempotent-Replayed: true:
the rows do not run again and the rate limit is not spent. The same
key with a different body or on another route is
409 idempotency_key_conflict; a repeat while the first request is
still running is 409 idempotency_key_in_progress. A request refused
before the rows run (400, 429) stores nothing.
Order of checks: key → scope → Idempotency-Key → stored outcome →
rate limit → body → rows.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
Idempotency-Keyrequired | header | string | 1 to 255 characters; a UUID per logical request is the usual choice.
Scoped to the API key that sends it. The first outcome is stored for 24
hours and replayed for a repeat with the same body; see each
operation. A longer key is refused with 400 validation_error.min length 1 · max length 255 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Request body application/json · required
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Per-row results, in request order. Returned even when every row is rejected. With Idempotent-Replayed, the stored results of the earlier request with this key.header Idempotent-Replayed — IdempotentReplayed | ProductBulkResponse |
400 | The request as a whole was refused, and nothing is stored against its
Idempotency-Key. idempotency_key_required when the header is
missing; validation_error when the header is longer than 255
characters, or when items is missing, not an
array, empty or over the row cap (details[].issue: missing | too_many).
A problem with an individual row is never a 400; it is that row's
rejected result. Malformed JSON comes back as BodyParserError. | · BulkBadRequest |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
409 | idempotency_key_conflict — this API key already used the
Idempotency-Key for a different body or operation; send a new key.
idempotency_key_in_progress — the first request with this key has
not stored its outcome yet; retry shortly. If it persists, check the
current state and send a new key. | ErrorEnvelope · IdempotencyConflict |
413 | The JSON body is over 100 KB. Split the batch. | BodyParserError · PayloadTooLarge |
429 | rate_limited — more than 60 requests to this route with this key in the current 600-second window.header Retry-After — RetryAfter | ErrorEnvelope · RateLimited |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl -X POST "https://api-staging.povito.com/partner/v1/products/bulk" \
-H "Authorization: Bearer povito_sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_sku": "ERP-1042",
"title": "Sony WH-1000XM5 Wireless Headphones",
"price_iqd": 425000,
"description": "Noise-cancelling over-ear headphones, black.",
"category_ids": [
"pcat_01K3W6H8M2P4R6T8V0X2Z4B6D8"
],
"initial_stock": 12
},
{
"external_sku": "ERP-1043",
"title": "Anker PowerCore 20000 (renamed)"
},
{
"external_sku": "ERP-1044",
"title": "USB-C cable 2 m"
}
]
}'{
"items": [
{
"external_sku": "ERP-1042",
"title": "Sony WH-1000XM5 Wireless Headphones",
"price_iqd": 425000,
"description": "Noise-cancelling over-ear headphones, black.",
"category_ids": [
"pcat_01K3W6H8M2P4R6T8V0X2Z4B6D8"
],
"initial_stock": 12
},
{
"external_sku": "ERP-1043",
"title": "Anker PowerCore 20000 (renamed)"
},
{
"external_sku": "ERP-1044",
"title": "USB-C cable 2 m"
}
]
}{
"results": [
{
"external_sku": "ERP-1042",
"status": "accepted",
"product_id": "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C"
},
{
"external_sku": "ERP-1043",
"status": "accepted",
"product_id": "prod_01K4P1C7E9G3J5L7N9Q1S3U5W7"
},
{
"external_sku": "ERP-1044",
"status": "rejected",
"error": {
"code": "validation_error",
"message": "title and price_iqd are required to create a new SKU."
}
}
]
}Inventory
Absolute stock and price sync, and audited stock adjustments.
PATCH /inventory/bulk#
Set absolute stock and prices for up to 500 SKUs
The route most integrations call on a schedule. Both fields are
absolute values, not deltas, and write no adjustment audit entry (use
POST /inventory/adjustments when the reason matters).
on_handsetsstocked_quantityat the seller's primary stock location only.price_iqdsets the seller's list price. It works only on a single-variant product; a multi-variant product is rejected withproduct_has_multiple_variants. During a markdown campaign the discounted price is recomputed from the new list price.
Within a row, on_hand is written before price_iqd. If the price
write then fails, the row is reported rejected even though the stock
write already happened.
Each row is validated before any of it is written, and a row that fails
is rejected with validation_error on its own — the rest of the
request still runs:
- a row that is not an object, or has a missing or blank
external_sku(its result hasexternal_sku: null; results are in request order); - neither
on_handnorprice_iqdset (nullcounts as not set); on_handthat is not a whole number 0 or more — negative, fractional, or not a JSON number ("12"is rejected, not dropped). Nothing in such a row is written, not even itsprice_iqd.
A price_iqd of the wrong JSON type is still dropped, and a row whose
only field it was reports accepted having changed nothing — send
numbers.
Idempotent. The first outcome is stored for 24 hours against the
Idempotency-Key and the API key that sent it. A repeat with the same
body gets the stored status and body back with Idempotent-Replayed: true:
nothing is written again — so a delayed retry cannot overwrite a newer
sync with older numbers — and the rate limit is not spent. The same key
with a different body or on another route is
409 idempotency_key_conflict; a repeat while the first request is
still running is 409 idempotency_key_in_progress. A request refused
before the rows run (400, 429) stores nothing. To re-send rows,
use a new key.
Order of checks: key → scope → Idempotency-Key → stored outcome →
rate limit → body → rows.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
Idempotency-Keyrequired | header | string | 1 to 255 characters; a UUID per logical request is the usual choice.
Scoped to the API key that sends it. The first outcome is stored for 24
hours and replayed for a repeat with the same body; see each
operation. A longer key is refused with 400 validation_error.min length 1 · max length 255 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Request body application/json · required
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Per-row results, in request order. Returned even when every row is rejected. With Idempotent-Replayed, the stored results of the earlier request with this key.header Idempotent-Replayed — IdempotentReplayed | InventoryBulkResponse |
400 | The request as a whole was refused, and nothing is stored against its
Idempotency-Key. idempotency_key_required when the header is
missing; validation_error when the header is longer than 255
characters, or when items is missing, not an
array, empty or over the row cap (details[].issue: missing | too_many).
A problem with an individual row is never a 400; it is that row's
rejected result. Malformed JSON comes back as BodyParserError. | · BulkBadRequest |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
409 | idempotency_key_conflict — this API key already used the
Idempotency-Key for a different body or operation; send a new key.
idempotency_key_in_progress — the first request with this key has
not stored its outcome yet; retry shortly. If it persists, check the
current state and send a new key. | ErrorEnvelope · IdempotencyConflict |
413 | The JSON body is over 100 KB. Split the batch. | BodyParserError · PayloadTooLarge |
429 | rate_limited — more than 60 requests to this route with this key in the current 600-second window.header Retry-After — RetryAfter | ErrorEnvelope · RateLimited |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl -X PATCH "https://api-staging.povito.com/partner/v1/inventory/bulk" \
-H "Authorization: Bearer povito_sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_sku": "ERP-1042",
"on_hand": 12,
"price_iqd": 419000
},
{
"external_sku": "ERP-1043",
"on_hand": 0
},
{
"external_sku": "ERP-2001",
"price_iqd": 35000
},
{
"external_sku": "ERP-2002",
"on_hand": -3
}
]
}'{
"items": [
{
"external_sku": "ERP-1042",
"on_hand": 12,
"price_iqd": 419000
},
{
"external_sku": "ERP-1043",
"on_hand": 0
},
{
"external_sku": "ERP-2001",
"price_iqd": 35000
},
{
"external_sku": "ERP-2002",
"on_hand": -3
}
]
}{
"results": [
{
"external_sku": "ERP-1042",
"status": "accepted"
},
{
"external_sku": "ERP-1043",
"status": "accepted"
},
{
"external_sku": "ERP-2001",
"status": "rejected",
"error": {
"code": "product_has_multiple_variants",
"message": "This product has more than one variant — use PUT /products/{id}/variants instead."
}
},
{
"external_sku": "ERP-2002",
"status": "rejected",
"error": {
"code": "validation_error",
"message": "on_hand must be a whole number of units, 0 or more."
}
}
]
}POST /inventory/adjustments#
Post up to 100 stock movements with a reason
Applies signed deltas and writes one audit-trail entry per item. The SKU-keyed counterpart of the seller dashboard's adjustment route.
All or nothing, not per row: an unknown SKU, an unknown
location_id, a SKU repeated in the request, or a delta that would take
stock below zero refuses the whole request and nothing is written.
category: external_sale with a negative delta and a unit_value posts
the outside-sale revenue (and cost of goods, where a unit cost is known)
to the seller's books; damaged with a negative delta posts a cost
write-off. Bookkeeping is best-effort and never fails the adjustment.
Idempotent. Idempotency-Key is required. The first request's
outcome is stored for 24 hours against that key and the API key that
sent it. A repeat with the same body gets the stored status and body
back with Idempotent-Replayed: true and moves no stock. Bodies are
compared as JSON, so member order and whitespace do not matter.
- The same key with a different body →
409 idempotency_key_conflict. - A repeat while the first request is still running →
409 idempotency_key_in_progress; retry shortly. If it persists, the first attempt's outcome is unknown: check stock withGET /products/{sku}and send a new key. - A request refused before anything is written — every
400,404and422below — stores nothing, so the body can be corrected and resent with the same key. - An unexpected failure (
500) may have come after a write, so it is stored and replayed like a success. Check stock before sending the movement again under a new key.
Order of checks: key → scope → Idempotency-Key → stored outcome →
body → SKUs and locations → write. No rate limit applies.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
Idempotency-Keyrequired | header | string | 1 to 255 characters; a UUID per logical request is the usual choice.
Scoped to the API key that sends it. The first outcome is stored for 24
hours and replayed for a repeat with the same body; see each
operation. A longer key is refused with 400 validation_error.min length 1 · max length 255 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Request body application/json · required
Responses
| Status | Meaning | Body |
|---|---|---|
200 | Every item was applied — by this request, or by the earlier request with the same Idempotency-Key when Idempotent-Replayed is set.header Idempotent-Replayed — IdempotentReplayed | InventoryAdjustmentResponse |
400 | idempotency_key_required — no Idempotency-Key header.
validation_error — Idempotency-Key longer than 255 characters;
reason missing; category not one of the four
values; unit_value sent without external_sale or not positive;
items empty or over 100; an external_sku missing; a delta that
is not a non-zero integer; or the same SKU twice
(details[].issue: duplicate_variant). | |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
404 | inventory_item_not_found — a SKU matches none of this seller's variants, or location_id is not one of this seller's locations. | ErrorEnvelope |
409 | idempotency_key_conflict — this API key already used the
Idempotency-Key for a different body or operation; send a new key.
idempotency_key_in_progress — the first request with this key has
not stored its outcome yet; retry shortly. If it persists, check the
current state and send a new key. | ErrorEnvelope · IdempotencyConflict |
413 | The JSON body is over 100 KB. Split the batch. | BodyParserError · PayloadTooLarge |
422 | validation_error — a delta would take stock below zero (details[].issue: negative_result), or the seller has no stock location configured. | ErrorEnvelope |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl -X POST "https://api-staging.povito.com/partner/v1/inventory/adjustments" \
-H "Authorization: Bearer povito_sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "Weekly stocktake, Karrada branch",
"category": "damaged",
"items": [
{
"external_sku": "ERP-1042",
"delta": -2
},
{
"external_sku": "ERP-1043",
"delta": 5,
"location_id": "sloc_01K2F4H6K8M0P2R4T6W8Y0A2C4"
}
]
}'{
"reason": "Weekly stocktake, Karrada branch",
"category": "damaged",
"items": [
{
"external_sku": "ERP-1042",
"delta": -2
},
{
"external_sku": "ERP-1043",
"delta": 5,
"location_id": "sloc_01K2F4H6K8M0P2R4T6W8Y0A2C4"
}
]
}{
"reason": "Weekly stocktake, Karrada branch",
"category": "damaged",
"data": [
{
"variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8",
"before": 12,
"after": 10,
"delta": -2
},
{
"variant_id": "variant_01K4P1C8F0H2K4M6P8R0T2V4X6",
"before": 0,
"after": 5,
"delta": 5
}
]
}Orders
Pull the seller's orders. There are no order webhooks and no order actions.
GET /orders#
List the seller's orders
One row per order placed with this seller (the seller's child order of a multi-seller checkout), newest first. Other sellers' lines on the same checkout are never visible.
Rows are driven by the seller's settlement record for each order: the
cursor, since and the row's created_at all refer to when that record
was created, which is when the order was placed with this seller.
status is the same seller-facing status GET /orders/{id} reports,
so a shipped order reads shipped here too.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
since | query | string (date-time) | Only orders placed with this seller at or after this instant —
the filter is on the seller's settlement record's creation time (the
row's An RFC 3339 date-time with a zone: |
cursor | query | string | The next_cursor of the previous page; omit for the first page. Opaque. |
limit | query | integer | Orders per page. A value that is not a positive integer is ignored and the default applies; values above 200 are capped at 200. default 50 · min 1 · max 200 |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | One page of orders. | OrderPage |
400 | validation_error — since is not an RFC 3339 date-time with a zone (details[]: {field: since, issue: invalid}). | ErrorEnvelope |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl "https://api-staging.povito.com/partner/v1/orders" \
-H "Authorization: Bearer povito_sk_test_..."{
"data": [
{
"order_id": "order_01K4T2B6D8F0H2K4M6P8R0T2V4",
"increment_id": "POV-100045",
"status": "shipped",
"total": 435000,
"currency": "IQD",
"created_at": "2026-09-10T08:12:04.000Z"
}
],
"next_cursor": "stlc_01K4T2B7E9G1J3L5N7Q9S1U3W5",
"has_more": true
}GET /orders/{id}#
Get one order with its lines and settlement
Every line carries external_sku, the seller's SKU at the time of the
order. Accepting, shipping and cancelling happen in the seller
dashboard, not here.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
idrequired | path | string | The order_id from GET /orders. |
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The order. | OrderDetail |
401 | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope · Unauthorized |
403 | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope · Forbidden |
404 | order_not_found — no such order, or it belongs to another seller. | ErrorEnvelope |
500 | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope · InternalError |
Examples
curl "https://api-staging.povito.com/partner/v1/orders/{id}" \
-H "Authorization: Bearer povito_sk_test_..."{
"order_id": "order_01K4T2B6D8F0H2K4M6P8R0T2V4",
"increment_id": "POV-100045",
"status": "shipped",
"total": 435000,
"currency": "IQD",
"shipping_address": {
"first_name": "Zainab",
"last_name": "Al-Rubaie",
"address_1": "Karrada, Street 62, House 14",
"city": "Baghdad",
"phone": "+9647701234567"
},
"items": [
{
"item_id": "ordli_01K4T2B6E0G2J4L6N8Q0S2U4W6",
"title": "Sony WH-1000XM5 Wireless Headphones",
"quantity": 1,
"unit_price": 425000,
"variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8",
"external_sku": "ERP-1042"
}
],
"accepted_at": "2026-09-10T09:01:30.000Z",
"shipped_at": "2026-09-10T14:20:00.000Z",
"tracking_number": "HX-88412093",
"carrier": "hiexpress",
"created_at": "2026-09-10T08:12:04.000Z",
"settlement": {
"status": "pending_delivery",
"commission_percent": 8,
"commission_amount": 34000,
"earnings": 391000,
"hold_days": 7,
"hold_starts_at": null,
"funds_released_at": null,
"delivery_confirmation_source": null,
"delivery_failed_at": null,
"delivery_failure_reason": null
}
}Shared parameters#
IdempotencyKey#
| Name | In | Type | Notes |
|---|---|---|---|
Idempotency-Keyrequired | header | string | 1 to 255 characters; a UUID per logical request is the usual choice.
Scoped to the API key that sends it. The first outcome is stored for 24
hours and replayed for a repeat with the same body; see each
operation. A longer key is refused with 400 validation_error.min length 1 · max length 255 |
RequestId#
| Name | In | Type | Notes |
|---|---|---|---|
X-Request-Id | header | string | Your correlation id, echoed as error.request_id on error responses. |
Cursor#
| Name | In | Type | Notes |
|---|---|---|---|
cursor | query | string | The next_cursor of the previous page; omit for the first page. Opaque. |
Sku#
| Name | In | Type | Notes |
|---|---|---|---|
skurequired | path | string | The seller's own SKU (product_variant.sku), URL-encoded.min length 1 |
Response headers#
RetryAfter#
integer — Seconds until the current rate-limit window resets.
IdempotentReplayed#
string — true when this response is the stored outcome of an earlier request with the same Idempotency-Key; absent otherwise.
Error responses#
| Name | Meaning | Body |
|---|---|---|
BulkBadRequest | The request as a whole was refused, and nothing is stored against its
Idempotency-Key. idempotency_key_required when the header is
missing; validation_error when the header is longer than 255
characters, or when items is missing, not an
array, empty or over the row cap (details[].issue: missing | too_many).
A problem with an individual row is never a 400; it is that row's
rejected result. Malformed JSON comes back as BodyParserError. | ErrorEnvelope or BodyParserError |
Unauthorized | invalid_api_key — no Authorization: Bearer key, a malformed or
unknown key, a wrong secret, or a revoked key.
api_key_wrong_environment — a well-formed test key sent to Live or
live key sent to Sandbox. | ErrorEnvelope |
Forbidden | insufficient_scope — the key is valid but lacks the scope this operation checks. | ErrorEnvelope |
IdempotencyConflict | idempotency_key_conflict — this API key already used the
Idempotency-Key for a different body or operation; send a new key.
idempotency_key_in_progress — the first request with this key has
not stored its outcome yet; retry shortly. If it persists, check the
current state and send a new key. | ErrorEnvelope |
RateLimited | rate_limited — more than 60 requests to this route with this key in the current 600-second window.header Retry-After | ErrorEnvelope |
PayloadTooLarge | The JSON body is over 100 KB. Split the batch. | BodyParserError |
InternalError | internal_error — unexpected server failure. Safe to retry reads with backoff. On the three write routes it is stored against the Idempotency-Key and replayed, because the failure may have come after a write: check the current state, then resend under a new key. Log request_id. | ErrorEnvelope |
Schemas#
Iqd#
Whole Iraqi dinars. IQD has no minor units.
integer — Whole Iraqi dinars. IQD has no minor units.
e.g. 425000
Timestamp#
string (date-time)
e.g. "2026-09-10T08:12:04.000Z"
ErrorEnvelope#
| Field | Type | Notes | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
errorrequired | object | fields
|
ErrorDetail#
| Field | Type | Notes |
|---|---|---|
field | string | e.g. "items[3].external_sku" |
issuerequired | string | Values used by this API: missing, invalid, too_many, empty, not_applicable, duplicate_variant, negative_result.e.g. "missing" |
ErrorCode#
Codes a top-level error from this API can carry.
string — Codes a top-level error from this API can carry.
one of "validation_error", "idempotency_key_required", "idempotency_key_conflict", "idempotency_key_in_progress", "invalid_api_key", "api_key_wrong_environment", "insufficient_scope", "product_not_found", "inventory_item_not_found", "order_not_found", "rate_limited", "internal_error"
BodyParserError#
Returned by the HTTP framework before Povito's handlers run — malformed
JSON or an oversized body. Has no code or request_id.
| Field | Type | Notes |
|---|---|---|
messagerequired | string | |
typerequired | string | e.g. "invalid_data" |
Health#
| Field | Type | Notes |
|---|---|---|
statusrequired | string | always "ok" |
apirequired | string | always "partner" |
versionrequired | string | always "v1" |
currencyrequired | string | always "IQD" |
ProductStatus#
draft (created, not submitted), proposed (awaiting review), published (live), rejected (see rejection_reason).
string — draft (created, not submitted), proposed (awaiting review), published (live), rejected (see rejection_reason).
one of "draft", "proposed", "published", "rejected"
Product#
| Field | Type | Notes |
|---|---|---|
idrequired | string | e.g. "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C" |
titlerequired | string | |
handlerequired | string | |
statusrequired | ProductStatus | |
descriptionrequired | string | null | |
thumbnailrequired | string | null (uri) | |
imagesrequired | array of | |
category_idsrequired | array of | |
brandrequired | Brand or null | |
variantsrequired | array of | Every variant this seller sells the product as, newest offer first — on GET /products and GET /products/{sku} alike. |
catalog_managedrequired | boolean | A Povito catalog product. Its title, description, categories and brand cannot be changed through this API. |
handlingrequired | string | Courier handling hint. one of "none", "fragile", "sealed", "cold" |
rejection_reasonrequired | string | null | |
created_atrequired | Timestamp | |
updated_atrequired | Timestamp |
Brand#
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
namerequired | string | |
slugrequired | string |
Variant#
One variant as this seller sells it — the seller's offer on that variant.
| Field | Type | Notes |
|---|---|---|
offer_idrequired | string | |
variant_idrequired | string | |
skurequired | string | The seller's own SKU — the value to use as external_sku. |
optionsrequired | map of | Option title to value, e.g. {"Size": "M"}. Products created through this API have {"Default": "Default"}. |
pricerequired | Iqd | The seller's list price. Stays the list price while a markdown runs. |
sale_pricerequired | Iqd or null | The discounted price while a Povito markdown campaign runs on this variant, otherwise null. |
campaign_labelrequired | string | null | |
stocked_quantityrequired | integer | On-hand units summed across all of this seller's stock locations. Not reserved-adjusted. Always a whole number; a fractional stored level (possible only through writes outside Povito's seller and partner APIs) is rounded to the nearest unit per location. min 0 |
conditionrequired | string | null | |
notesrequired | string | null |
ProductPage#
| Field | Type | Notes |
|---|---|---|
datarequired | array of | |
next_cursorrequired | string | null | |
has_morerequired | boolean |
ProductBulkItem#
| Field | Type | Notes |
|---|---|---|
external_skurequired | string | Trimmed before use. min length 1 |
title | string | Required when creating. |
price_iqd | Iqd | Required when creating; ignored when the SKU already exists. |
description | string | |
category_ids | array of | Replaces the product's categories. A catalog-only category rejects the row. |
initial_stock | integer | Units at the seller's first stock location when creating; default 0. Ignored when the SKU already exists. |
ProductBulkRequest#
| Field | Type | Notes |
|---|---|---|
itemsrequired | array of | at least 1 item · at most 50 items |
ProductBulkAccepted#
| Field | Type | Notes |
|---|---|---|
external_skurequired | string | |
statusrequired | string | always "accepted" |
product_idrequired | string |
InventoryBulkAccepted#
| Field | Type | Notes |
|---|---|---|
external_skurequired | string | |
statusrequired | string | always "accepted" |
BulkRowRejected#
| Field | Type | Notes | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
external_skurequired | string | null | The row's trimmed external_sku, or null when the row had none (or was not an object). Results are in request order. | |||||||||
statusrequired | string | always "rejected" | |||||||||
errorrequired | object | A single line, never details or request_id.fields
|
BulkRowErrorCode#
validation_error — the row is not an object or has no external_sku;
a create without title/price_iqd; on /inventory/bulk, neither
on_hand nor price_iqd set, or an on_hand that is not a whole
number 0 or more; or the seller has no stock location.
product_not_found / inventory_item_not_found —
the SKU is not this seller's. product_has_multiple_variants — price on
a multi-variant product. catalog_only_category — a category that
accepts only Povito catalog products. catalog_product_locked — editing
a catalog product's content. internal_error — anything unexpected; the
message is the underlying error's.
string — validation_error — the row is not an object or has no external_sku;
a create without title/price_iqd; on /inventory/bulk, neither
on_hand nor price_iqd set, or an on_hand that is not a whole
number 0 or more; or the seller has no stock location.
product_not_found / inventory_item_not_found —
the SKU is not this seller's. product_has_multiple_variants — price on
a multi-variant product. catalog_only_category — a category that
accepts only Povito catalog products. catalog_product_locked — editing
a catalog product's content. internal_error — anything unexpected; the
message is the underlying error's.
one of "validation_error", "product_not_found", "inventory_item_not_found", "product_has_multiple_variants", "catalog_only_category", "catalog_product_locked", "internal_error"
ProductBulkResponse#
| Field | Type | Notes |
|---|---|---|
resultsrequired | array of |
InventoryBulkItem#
One of:
- any
- any
InventoryBulkRequest#
| Field | Type | Notes |
|---|---|---|
itemsrequired | array of | at least 1 item · at most 500 items |
InventoryBulkResponse#
| Field | Type | Notes |
|---|---|---|
resultsrequired | array of |
AdjustmentCategory#
string
one of "external_sale", "damaged", "restock", "other"
InventoryAdjustmentItem#
| Field | Type | Notes |
|---|---|---|
external_skurequired | string | min length 1 |
deltarequired | integer | Signed, non-zero. |
location_id | string | One of the seller's stock locations; defaults to the primary location. |
InventoryAdjustmentRequest#
| Field | Type | Notes |
|---|---|---|
reasonrequired | string | Trimmed; stored on every audit entry. min length 1 |
categoryrequired | AdjustmentCategory | |
unit_value | number | What one unit actually sold for, in IQD. Accepted only with category external_sale. |
itemsrequired | array of | Each SKU at most once. at least 1 item · at most 100 items |
InventoryAdjustmentResult#
| Field | Type | Notes |
|---|---|---|
variant_idrequired | string | |
beforerequired | integer | Stocked quantity at the target location before the adjustment, in whole units. A fractional stored level is rounded to the nearest unit, and the adjustment writes after, so it also settles the level to a whole number.min 0 |
afterrequired | integer | The stocked quantity written. Always before + delta.min 0 |
deltarequired | integer |
InventoryAdjustmentResponse#
| Field | Type | Notes |
|---|---|---|
reasonrequired | string | |
categoryrequired | AdjustmentCategory | |
datarequired | array of |
OrderSummary#
| Field | Type | Notes |
|---|---|---|
order_idrequired | string | |
increment_idrequired | string | null | The shopper-facing checkout number, e.g. POV-100045. |
statusrequired | string | null | Identical to GET /orders/{id}'s status: shipped once the seller has shipped; otherwise Medusa's status (pending, completed, canceled, archived, …). null if the order record could not be read. |
totalrequired | Iqd | This seller's order total including shipping. 0 if the order record could not be read. |
currencyrequired | string | always "IQD" |
created_atrequired | Timestamp | When the order was placed with this seller (its settlement record's creation time). |
OrderPage#
| Field | Type | Notes |
|---|---|---|
datarequired | array of | |
next_cursorrequired | string | null | |
has_morerequired | boolean |
OrderLine#
| Field | Type | Notes |
|---|---|---|
item_idrequired | string | |
titlerequired | string | |
quantityrequired | integer | min 1 |
unit_pricerequired | Iqd | |
variant_idrequired | string | null | |
external_skurequired | string | null |
ShippingAddress#
Missing parts are empty strings, never null.
| Field | Type | Notes |
|---|---|---|
first_namerequired | string | |
last_namerequired | string | |
address_1required | string | |
cityrequired | string | |
phonerequired | string |
OrderSettlement#
What the seller will be paid for this order and where that money is.
| Field | Type | Notes |
|---|---|---|
statusrequired | string | one of "pending_capture", "pending_split", "pending_delivery", "hold_active", "released", "refunded", "returned" |
commission_percentrequired | number | |
commission_amountrequired | Iqd | |
earningsrequired | Iqd | The seller's credit for this order after commission. |
hold_daysrequired | integer | null | |
hold_starts_atrequired | string | null (date-time) | |
funds_released_atrequired | string | null (date-time) | |
delivery_confirmation_sourcerequired | string or null | |
delivery_failed_atrequired | string | null (date-time) | Set when the courier or an admin reported the order will never be delivered. |
delivery_failure_reasonrequired | string | null |
OrderDetail#
| Field | Type | Notes |
|---|---|---|
order_idrequired | string | |
increment_idrequired | string | The shopper-facing checkout number; falls back to order_id for orders without one. |
statusrequired | string | shipped once the seller has shipped; otherwise Medusa's status (pending, completed, canceled, archived, …). |
totalrequired | Iqd | This seller's order total including shipping. |
currencyrequired | string | always "IQD" |
shipping_addressrequired | ShippingAddress or null | |
itemsrequired | array of | |
accepted_atrequired | string | null (date-time) | |
shipped_atrequired | string | null (date-time) | |
tracking_numberrequired | string | null | |
carrierrequired | string | null | |
created_atrequired | Timestamp | |
settlementrequired | OrderSettlement |
Examples#
Product#
A single-variant product created through this API
{
"id": "prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C",
"title": "Sony WH-1000XM5 Wireless Headphones",
"handle": "sony-wh-1000xm5-wireless-headphones",
"status": "published",
"description": "Noise-cancelling over-ear headphones, black.",
"thumbnail": "https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg",
"images": [
"https://cdn.povito.com/static/products/prod_01K4R8Z2Q6M3VYB7TD1N5HXJ2C/1.jpg"
],
"category_ids": [
"pcat_01K3W6H8M2P4R6T8V0X2Z4B6D8"
],
"brand": {
"id": "brand_01K3Y2A4C6E8G0J2L4N6Q8S0U2",
"name": "Sony",
"slug": "sony"
},
"variants": [
{
"offer_id": "offer_01K4R8Z3A1B2C3D4E5F6G7H8J9",
"variant_id": "variant_01K4R8Z2S8T4W6Y8A0C2E4G6J8",
"sku": "ERP-1042",
"options": {
"Default": "Default"
},
"price": 425000,
"sale_price": 382500,
"campaign_label": "Back to School",
"stocked_quantity": 12,
"condition": null,
"notes": null
}
],
"catalog_managed": false,
"handling": "none",
"rejection_reason": null,
"created_at": "2026-09-01T10:00:00.000Z",
"updated_at": "2026-09-04T08:12:00.000Z"
}