ProductBox API
Send product lists as JSON, get back clean names, brands, barcodes, categories, descriptions, specifications and images — the same pipeline the web app runs, driven from your own systems.
Base URL https://productbox.net/api/public/v1
Authentication
Every request carries an API key as a bearer token. Create one under Account → API; it is shown once and stored only as a hash, so it cannot be recovered later — only replaced.
Authorization: Bearer pb_live_…Keys carry scopes. A read key can list and fetch; a read & write key can also create lists, start runs and spend credits. Give dashboards and monitoring a read key — a credential that cannot spend money is one you can deploy more freely.
If a header is easier than a bearer token in your integration platform, X-API-Key is accepted equivalently.
Quickstart
Three calls: create a list, wait for it, read the results. Enrichment searches the live web, so a list takes minutes — poll every 10–30 seconds, or let a webhook tell you.
# 1. create a list
curl -X POST https://productbox.net/api/public/v1/lists \
-H "Authorization: Bearer $PRODUCTBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "august restock",
"language": "en",
"auto_accept_confidence": 0.92,
"run": true,
"products": [
{"id": "A-1001", "name": "Bosch MUMP1000 food processor",
"gtin": "4242005098453"},
{"id": "A-1002", "name": "Tefal SV4111 steam generator"}
]
}'
# => {"id":"c51472…","status":"queued","total_products":2, …}
# 2. poll until status is "completed"
curl https://productbox.net/api/public/v1/lists/c51472… \
-H "Authorization: Bearer $PRODUCTBOX_KEY"
# 3. pull the results
curl "https://productbox.net/api/public/v1/lists/c51472…/results?limit=100" \
-H "Authorization: Bearer $PRODUCTBOX_KEY"Lists
A list is a batch of products to enrich. Creating one is free — credits are spent per product as results are delivered, so a run that outlives your balance pauses at paused_insufficient_credits and resumes on the next /run after a top-up. No work is lost.
/listsname or a gtin — something searchable. id is your own key: it addresses the product everywhere else in the API and comes back on every result, so you never have to learn our row numbers. Any extra keys you send are carried through and returned untouched./lists/{id}/runrun: true at creation to skip this call./lists/{id}status is one of ready, queued, running, paused_insufficient_credits, paused_quality, completed, cancelled, failed./listslimit, offset)./lists/{id}/cancel/lists/{id}Options
Set these on POST /lists:
language— delivery language (ISO 639-1). Omit it and we detect it from your product names.config— what to extract:descriptions(0–2),spec_columns(0–10),images,serp_country.auto_accept_confidence— see Accepting & rejecting.webhook_url— see Webhooks.
Results
Results are cursor-paginated. Start at cursor=0 and follow next_cursor until it is null. A cursor rather than an offset means a product decided between two pages cannot shift the window and hide another one.
GET https://productbox.net/api/public/v1/lists/{id}/results?cursor=0&limit=200
{
"list_id": "c51472…",
"status": "completed",
"results": [
{
"id": "A-1001", // your id, as you sent it
"status": "enriched",
"confidence": 0.97,
"product": {
"canonical_name": "Bosch MUMP1000 Food Processor, White",
"brand": "Bosch",
"model": "MUMP1000",
"ean": "4242005098453",
"category": "Home & Kitchen / Food processors",
"description": "…",
"specs": [{"name": "Power", "value": "500 W"}],
"image_urls": ["https://productbox.net/api/v1/img/a/9f2c…"],
"source_count": 7, // independent sources that agreed
"input_warehouse": "STO-7" // your passthrough column, returned
}
}
],
"next_cursor": 1
}Per-product status is enriched (matched and filed into your library), review (matched, but the identity needs your confirmation), no_match (nothing trustworthy found — free) or pending. Filter with ?status=review.
Add ?languages=de,fr for translated copies alongside the originals. A language we have not cached yet answers 202 while it is prepared — retry shortly and it will be there.
Accepting & rejecting
Not every match is certain. A product whose identity we could not prove outright comes back as review with its confidence. It is already paid for — the credit is captured when the result is delivered, so accepting is free and you can use the data immediately.
If it is the wrong product, reject it within 14 days and 0.9 of the credit comes back; 0.1 covers the search and extraction already done.
/lists/{id}/products/{your_id}/accept/lists/{id}/products/{your_id}/rejectreason in the body. Refunds 0.9 credits inside the window.To skip the round trip entirely, set auto_accept_confidence on the list. Review products at or above that score are accepted automatically when the list finishes — but only if they are actually complete: a match with no usable description or an unverified image stays in review however high its identity score. We would rather hand you a decision than a bad record.
Instant lookup
One barcode, one call. If we already have the product it answers immediately; if nobody has enriched it yet it has to be searched for, which takes minutes — so that case answers 202 with a one-product list to poll.
GET https://productbox.net/api/public/v1/products/4242005098453
200 → {"source": "library", "credits_charged": 0, "product": {…}}
200 → {"source": "catalogue", "credits_charged": 1, "product": {…}}
202 → {"source": "enriching", "list": {"id": "…", "status": "queued"}}A product already in your library is free — you have paid for it once. Pass ?enrich=false to get a plain 404 on a miss instead of starting a search.
Webhooks
Set webhook_url on a list and we POST to it when the list finishes, so you do not have to poll. The callback is a signal, not a data channel: it carries the counts, and you pull the products from /results. That keeps one authoritative path for the data and means a delivery that arrives twice, late or out of order can never corrupt your state.
POST https://your-app.example.com/hooks/productbox
{
"event": "list.completed",
"list_id": "c51472…",
"name": "august restock",
"status": "completed",
"total_products": 2,
"counts": {"enriched": 1, "review": 1, "no_match": 0},
"credits_charged": 2.0,
"results_url": "/api/public/v1/lists/c51472…/results"
}Verify every delivery. Each carries an HMAC-SHA256 signature made with the signing secret shown when you created the key. Compare it in constant time, and reject stale timestamps.
# headers on every delivery
X-ProductBox-Event: list.completed
X-ProductBox-Delivery: 9f2c… # unique per delivery — use it to dedupe
X-ProductBox-Timestamp: 1787775002
X-ProductBox-Signature: sha256=<hmac>
# signature = HMAC-SHA256(secret, "<timestamp>." + <raw body>)Respond 2xx as soon as you have stored the event; do the work afterwards. We retry a failed delivery five times over about two hours, then stop. Revoking a key invalidates its signatures, so rotate the key and the receiver's secret together.
Errors & limits
Errors are standard HTTP codes with a detail message you can show or log.
| 400 | The request cannot be enriched as sent — an unsupported language, or no searchable product. |
| 401 | Missing, invalid or revoked key. |
| 402 | Out of credits. Top up and call /run again — the list resumes where it stopped. |
| 403 | The key lacks the scope, or the account is not verified. |
| 404 | No such list or product, on this account. |
| 409 | The list is not in a state where that makes sense (running a finished list, deciding a settled product). |
| 413 | Over 10,000 products, or a body over 32 MB, in one request — split it. |
| 429 | Rate limited: 120 requests a minute and 20,000 a day, per key. |
Limits are per key, not per account, so a runaway integration cannot throttle your other ones. Check GET /account for your balance before a large run rather than discovering it through a 402.
Request limits
- 10,000 products and 32 MB per
POST /lists. - 64 passthrough fields. Extra keys become columns, so they must be the same keys on every product —
sku_1,sku_2, … would make one column each. - 500 results per page, and at most 5 extra languages per results call.
- 120 requests a minute and 20,000 a day, per key.
Something missing?
Tell us what you are building and what the API does not let you do yet.
Create an API key