Reviews
Reference for human review through the API. List pending analyses with GET /v1/reviews, download the original with GET /v1/analyses/{id}/file and decide with POST /v1/analyses/{id}/review.
When an analysis needs a person to look at it, you can resolve it from the dashboard or from your own backend with these endpoints. Step-by-step guide in Human review from your backend.
| Method and path | What it does |
|---|---|
GET /v1/reviews | Lists analyses pending review (or already decided). |
GET /v1/analyses/{id}/file | Downloads the original document, if it is still stored. |
POST /v1/analyses/{id}/review | Approves or rejects an analysis pending review. |
They only work with a secret key (ck_test_… or ck_live_…). With a publishable key (pk_…) the response is
403 publishable_key_not_allowed.
When an analysis is pending review
An analysis with a verdict enters the review queue (review.status: "pending") when:
- its verdict is
review; - its verdict is
invalidand the template used asks for human review of invalid documents; - its verdict is
validand the template used does not approve valid documents on its own.
While it is pending, verdict.final_status is null. Once decided it becomes valid (approved) or invalid
(rejected). Without expect there is no verdict and the analysis cannot be reviewed (409 not_reviewable).
The analysis review object:
"review": {
"status": "pending",
"decision": null,
"assignee": null,
"reviewed_by": null,
"reviewed_at": null,
"reason": null,
"note": null
}| Field | Description |
|---|---|
status | pending, approved or rejected. |
decision | approve, reject or null if not decided yet. |
assignee | Team member it was assigned to in the dashboard, or null. |
reviewed_by | Who decided: the id of a dashboard user or, if decided through the API, the key id (key_…). |
reviewed_at | When it was decided. |
reason | Short reason for the decision (up to 200 characters). |
note | Internal note (up to 2000 characters). |
review is null for analyses that need no review.
List reviews
GET /v1/reviews| Parameter | Type | Description |
|---|---|---|
status | pending | decided | approved | rejected | all | Defaults to pending. decided means approved and rejected. |
type | string | Filters by document type, e.g. es_dni. |
template | string | Filters by template (tpl_…). |
metadata[key] | string | Filters by a metadata value. Repeatable: all must match. |
limit | integer 1–100 | Items per page. Default 10. |
starting_after | string | Id (an_…) of the last analysis of the previous page. |
It only includes analyses of your key's mode (test or live). Order:
pending: oldest first, like a queue: handle first what has been waiting longest.- The rest: newest first.
{
"object": "list",
"data": [
{
"id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3",
"object": "analysis",
"status": "completed",
"verdict": { "status": "review", "final_status": null, "…": "…" },
"review": { "status": "pending", "decision": null, "…": "…" },
"storage": { "mode": "review", "kept": true, "reason": "pending_review", "expires_at": "2026-10-29T10:05:01Z", "file_deleted_at": null },
"file_url": "https://api.constaia.com/v1/files/…",
"metadata": { "registration_id": "124" }
}
],
"has_more": false,
"url": "/v1/reviews"
}Each item is the full analysis object. Pagination works like
every other list: see Pagination.
curl -G https://api.constaia.com/v1/reviews \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
--data-urlencode "status=pending" \
--data-urlencode "metadata[event]=42" \
--data-urlencode "limit=50"Download the original
GET /v1/analyses/{id}/fileReturns the original document as it was uploaded, only if it is still stored (storage.kept: true). With the
default storage mode (review) that is the case while the analysis awaits review and during the margin after the
decision. See Storage and privacy.
200 response with the binary:
| Header | Value |
|---|---|
Content-Type | The original's: image/jpeg, image/png, image/webp, image/heic or application/pdf. |
Content-Disposition | inline; filename="an_….jpg" |
Cache-Control | private, no-store |
Errors:
| HTTP | code | When |
|---|---|---|
404 | resource_missing | The analysis doesn't exist or isn't from your account. |
404 | file_not_stored | The original was not stored (no review was needed or it was analysed with storage: "none") or it was already deleted after its window. param is id. The extracted data is still available. |
Keyless alternative: while the original is stored, the analysis file_url field holds a signed URL that expires after
15 minutes. It is handy to show the document on your review screen; don't store it, fetch the analysis again when you
need a new one.
curl -o original.jpg \
https://api.constaia.com/v1/analyses/an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3/file \
-H "Authorization: Bearer $CONSTAIA_API_KEY"Decide a review
POST /v1/analyses/{id}/review| Field | Type | Description |
|---|---|---|
decision | approve | reject | Required. |
reason | string (≤ 200) | null | Short reason, e.g. "Data matches the form". Optional. |
note | string (≤ 2000) | null | Internal note. Optional. |
Responds 200 with the updated analysis object:
review.status:approvedorrejected;review.decision:approveorreject.review.reviewed_by: the id of the key you decided with (key_…);review.reviewed_at,review.reasonandreview.note.verdict.final_status:validif you approve,invalidif you reject.storage.expires_atbecomes the decision time plusreview_retention_hours_after_decision(24 h by default). If that window is0, the original is deleted on decision andstorage.keptbecomesfalse.
The analysis.reviewed webhook is also sent, and the decision is recorded in the account's audit
log under the API key.
{
"id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3",
"object": "analysis",
"verdict": { "status": "review", "final_status": "valid", "…": "…" },
"review": {
"status": "approved",
"decision": "approve",
"assignee": null,
"reviewed_by": "key_01J9Z8Q3K4M5N6P7Q8R9S0T1V9",
"reviewed_at": "2026-09-30T08:12:40Z",
"reason": "Data matches the form",
"note": null
},
"storage": { "mode": "review", "kept": true, "reason": null, "expires_at": "2026-10-01T08:12:40Z", "file_deleted_at": null }
}Safe retries
- Send the
Idempotency-Keyheader: the same key with the same body returns the same response, with theIdempotent-Replayed: trueheader. See Idempotency. - Repeating the same decision on an already decided analysis returns the analysis as is (
200) and sends no new webhook. - A different decision on an already decided analysis returns
409 already_reviewed. Only an owner or admin can change it, from the dashboard.
Errors
| HTTP | code | When |
|---|---|---|
403 | publishable_key_not_allowed | You used a publishable key (pk_…). |
404 | resource_missing | The analysis doesn't exist or isn't from your account. |
409 | already_reviewed | The analysis already has a different decision. |
409 | not_reviewable | The analysis has no verdict: no expect was given or it hasn't finished yet. |
422 | invalid_parameter | The body is not valid (decision missing or not approve/reject, reason or note too long). |
curl https://api.constaia.com/v1/analyses/an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3/review \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: review-an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3" \
-d '{ "decision": "approve", "reason": "Data matches the form" }'Templates and retention windows
If you use templates, each one can set its own storage and review windows. The template object always includes these
fields:
| Field | Type | Description |
|---|---|---|
storage | none | review | temporary | persistent | Storage mode of analyses made with the template. |
review_retention_hours_after_decision | integer 0–720 | null | Hours the original is kept after the decision. null = the account value (24 by default). |
review_max_days | integer 1–90 | null | Maximum days a pending original is kept if nobody decides. null = the account value (30 by default). |
Account values are changed in the dashboard (Account settings).
Next steps
Stored analyses
Retrieve, list with filters and cursor pagination, export and delete analyses with /v1/analyses, and download exports through signed /v1/files URLs.
POST /v1/batches
Reference for POST /v1/batches: analyse up to 100 documents in one asynchronous call, with common or per-document options and a combined export.