https://api.decisionnode.com /v1/collections/{id}/itemscurl -X POST https://api.decisionnode.com/v1/collections/col_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80/items \
-H "Authorization: Bearer $DECISIONNODE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image":{"id":"badge","url":"https://files.example.com/staff/emp-1042.jpg"},"reference":"emp-1042","label":"Anna, reception"}'The SDKs send a local picture inline or as an upload, as a decision's, and a fresh Idempotency-Key with each call. bynn-decisionnode 0.3.2, decision-node-go v0.4.1 and bynn/decisionnode 0.3.1 send no metadata: to set metadata, send the body below over HTTPS.
curl -X POST https://api.decisionnode.com/v1/collections/col_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80/items \
-H "Authorization: Bearer $DECISIONNODE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"image":{"id":"badge","upload_id":"upl_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80"},"reference":"emp-1042","label":"Anna, reception","metadata":{"department":"reception","badge":1042,"visitor":false}}'Headers#
AuthorizationheaderrequiredBearer dn_live_.... A Full access key: every call acts for that key's workspace only, and a Decisions-only key gets403key_scope_denied, "This key may only make decisions."Content-Typeheaderapplication/json. Not checked: the body is read as JSON whatever its type.Idempotency-Keyheader- 1 to 255 printable ASCII characters of your choosing, sent once. A retry with the same key, route and body within 24 hours gets the first answer again; see Retrying.
Path parameters#
idstringrequired- The collection:
col_and 32 hex digits for your own, or a public collection's name,celebrities.
Request body#
{
"image": {
"id": "badge",
"upload_id": "upl_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80"
},
"reference": "emp-1042",
"label": "Anna, reception",
"metadata": { "department": "reception", "badge": 1042, "visitor": false }
}imageimage objectrequired- One picture: an
id(at most 64 characters) and exactly one ofurl,data(base64) orupload_id(with an optionalmedia_type), under the same image limits as a decision. In a zero-retention workspace an upload is consumed once the answer is the picture's own (a201,no_faceorimage_unreadable), never on a529or500. referencestringrequired- Your key for the item: 1 to 64 characters of
A-Z,a-z,0-9and_ . : -. It may repeat within a collection. labelstring or null- 1 to 128 characters with no control or invisible characters, returned in a match. Left out or
null, none. metadataobject- Your flat metadata, rules below. Left out,
{}.
Metadata#
- A flat object of at most 20 keys,
{}for none. - Keys are 1 to 40 characters of
a-z,0-9and_. - Values are text (at most 500 characters, no control or invisible characters), a number (under 10^15 in size, and 0 or at least 10^-6) or
trueorfalse. Nevernull, an array or an object. - At most 4,096 bytes as compact JSON.
- What it is for: a match on your own collection carries the item's
metadata, and the model reads it as evidence beside the score; it is present only on a reading from a worker on the dn-evidence-4 format, so read it as optional. Keep special categories of personal data out of it unless your purpose needs them.
loc | msg |
|---|---|
["body", "metadata"] | "Metadata is an object of keys and values." |
["body", "metadata"] | "Metadata holds at most 20 keys." |
["body", "metadata"] | "A metadata key is 1 to 40 characters of a-z, 0-9 and _: \"<key>\"." |
["body", "metadata", "<key>"] | "A metadata text is at most 500 characters." |
["body", "metadata", "<key>"] | "A metadata text holds no control or invisible characters." |
["body", "metadata", "<key>"] | "A metadata number is under 10^15 in size, and 0 or at least 10^-6." |
["body", "metadata", "<key>"] | "A metadata value is a text, a number or true or false." |
["body", "metadata"] | "Metadata is at most 4096 bytes as JSON." |
Response#
201 Created with the item. The picture is read and the face model runs once, in at most 10 seconds; a picture with several faces enrolls the largest. Only the face's template is kept, never the picture.
{
"id": "itm_9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d",
"collection": "col_3f6c1a9e0b2d4c7f8a1e5d9b2c4f6a80",
"reference": "emp-1042",
"label": "Anna, reception",
"metadata": { "department": "reception", "badge": 1042, "visitor": false },
"faces_found": 1,
"box": { "x1": 412, "y1": 220, "x2": 580, "y2": 470 },
"created_at": "2026-10-12T09:00:00Z"
}The enrolled item
id, reference, label, metadata, created_atitem fields- As on the item object.
collectionstring- The collection's id.
faces_foundinteger- How many faces the picture held that an enrollment can take, at most 64: each one the detector is at least 0.7 sure of and at least 1,000 square pixels in size.
boxobject- The enrolled face's corners
x1,y1,x2,y2, in pixels of the picture as sent. usageobjectenrollments, 1, andcost, what it booked:usdandboosters_usdthe enrollment's price,model_usd0 andfree_tierfalse, since the free tier never covers an enrollment.
What is billed#
| Answer | Billed |
|---|---|
201, a face enrolled | Yes |
422 no_face: the picture holds no face an enrollment can take | Yes: the face model ran |
422 image_unreadable: not a JPEG, PNG or WebP, over the image limits, a url not reached in time, or more than 1,024 faces | No |
400 image_url_refused, an upload's 400, or any check before the picture is read (402, 403, 404, 409) | No |
529, any reason | No |
A replay of the same Idempotency-Key | Once, for the first answer |
{
"detail": [
{
"type": "no_face",
"loc": ["body", "image"],
"msg": "No face was found in the image.",
"input": "badge"
}
]
}When enrollment cannot run#
detail.details.reason | Message | Retry |
|---|---|---|
enrollment_unpriced | "Face enrollment is not available on this deployment yet." | Never: it comes with no Retry-After, and a retry does not cure it. Enrollment is not offered here yet |
face_model_unavailable | "The face model is not available right now. Retry later." | After Retry-After (1 s), with backoff |
| none | "Overloaded. Retry after 1 s." | After Retry-After (1 s): no room right now, for the face model or the collections |
{
"detail": {
"error_type": "overloaded_error",
"message": "Face enrollment is not available on this deployment yet.",
"details": { "reason": "enrollment_unpriced" }
}
}Errors#
| Status | Type | When | What to do |
|---|---|---|---|
400 | api_usage_error | A malformed Idempotency-Key: "Idempotency-Key must be sent once, as 1 to 255 printable ASCII characters." | Send one key of 1 to 255 printable ASCII characters |
400 | image_url_refused | A url we do not fetch, as on a decision; or an upload's own 400 | Send the picture another way |
401 | authentication_error | The key is missing, unknown or revoked | Check the Authorization header |
402 | insufficient_credit | The prepaid balance is empty. Reads, edits and deletes stay open | Add credit, then retry |
403 | key_scope_denied | A Decisions-only key: "This key may only make decisions." | Use a Full access key on your server |
403 | collection_read_only | The collection is a public one: "<name> is a public collection and cannot be changed or listed." | Act on one of your own collections |
403 | collections_region_unavailable | Your workspace's storage location is not the default | Write to us |
403 | workspace_suspended | The workspace is suspended: "This workspace is suspended. Contact hello@bynn.com." | Write to us |
404 | collection_not_found | No such collection for your workspace; another workspace's reads the same. "Collection not found: <id>.", the id repeated only when it has the form of one | Check the id |
409 | items_limit_reached | Your workspace has 1,000,000 items in its collections, unless we set yours otherwise: "This workspace has reached its limit of <n> items in its collections." | Delete items, or write to us |
409 | idempotency_key_reused, idempotency_in_progress | The key was used with another request, or its first request still runs (Retry-After: 1) | See Retrying |
408 | request_timeout_error | The request body did not arrive in time. | Send it again |
413 | request_too_large_error | The body is over 26,214,400 bytes (25 MiB): "The request body is over 26,214,400 bytes (25 MiB). Send large images as uploads (POST /v1/uploads) and name them by upload_id." | Send the picture by url or as an upload |
422 | a detail list | A field out of bounds (image, reference, label, metadata); no_face, billed; image_unreadable, "The image could not be read: <why>.", not billed | Fix the field or the picture |
422 | a detail list | The body is not JSON (json_invalid) | Send a JSON object |
429 | rate_limit_error | Over your request rate | Wait Retry-After, then send the same call again |
500 | api_error | A fault on our side, the face model's included | Retry with backoff |
529 | overloaded_error | See When enrollment cannot run | As its reason says |
A refusal is {"detail": {"error_type": "...", "message": "..."}}; a 422 is {"detail": [...]}, one entry per field, each with type, loc, msg and input (a string cut to 64 characters, an object or array never echoed). Every 429 and 529 and the 409 idempotency_in_progress carry Retry-After (seconds) and retry-after-ms (milliseconds); enrollment_unpriced carries neither. The Collections API's own refusals (api_usage_error, collection_read_only, item_not_found, collections_region_unavailable, the limits, the idempotency 409s and image_url_refused) also carry x-decisionnode-error, the message in ASCII. Retry 408, 429, 529 and any 5xx after Retry-After; fix the call for the other 4xx.