v2 Response Contract Migration
This page documents an update to the response contract of the public /v2 endpoints. It fixes several inconsistencies between the API’s actual behavior and what was documented: field types, error structure, authentication responses.
Summary of changes
Section titled “Summary of changes”| Endpoint | Change |
|---|---|
POST /v2/extractor[/:agentReference] |
Errors are now enveloped with code ; metadata.duration changes from string to number |
POST /v2/extractor (global) |
data now initializes to [] (no longer null) ; metadata.thread_id added |
POST /v2/extractor/async[/:agentReference] |
Immediate response simplified to {status, extract_id} ; failure callback and webhook are now enveloped with code and a numeric detail.http_status |
POST /v2/documentalist/:agentReference |
Deprecated (sunset 2026-11-30) — stays an HTTP 200 text stream ; replaced by /v2/knowledge |
POST /v2/knowledge/:agentReference |
New — standard JSON response with real error HTTP statuses (same parameters as /v2/documentalist) |
GET /v2/token |
401 now returns an error envelope (instead of an empty body) |
| All protected routes | 401/403 rejections return an error envelope (instead of {message: "Unauthorized"}) |
Organization endpoints (/v2/organization/webhooks, /v2/organization/usage) |
The organization-not-found 404 error code changes from agent_not_found to organization_not_found |
Extractor
Section titled “Extractor”Synchronous errors (/v2/extractor and /v2/extractor/:agentReference)
Section titled “Synchronous errors (/v2/extractor and /v2/extractor/:agentReference)”Before — a generic message, identical regardless of the actual cause of failure:
{ "status": "error", "error": "Error while processing the extraction"}After
{ "status": "error", "code": "extraction_failed", "error": "Extraction stream failed", "detail": { }}code is now a stable machine identifier (see the error codes table) — error remains a human-readable message that can change without notice. detail is optional and absent most of the time.
Document with no extractable data
Section titled “Document with no extractable data”Before — a document the engine had processed without extracting anything returned 502 extraction_failed, as if the backend had failed.
After
{ "status": "error", "code": "no_data_extracted", "error": "No data could be extracted from the document"}The file was processed: submitting it again unchanged yields the same outcome. 502 extraction_failed is now reserved for engine failures. On /v2/extractor/async, the callback and the extraction.completed webhook carry the same code, with detail.http_status: 422.
metadata.duration: string → number
Section titled “metadata.duration: string → number”Before
{ "metadata": { "duration": "3.45", "tokens_consumed": 1 } }After
{ "metadata": { "duration": 3.45, "tokens_consumed": 1 } }If your client already parses duration with parseFloat/Number() defensively, no change is required. If you concatenate or compare the value as a string, update the typing.
Global extractor: data and thread_id
Section titled “Global extractor: data and thread_id”Before — on a stream read failure, data could be null and metadata did not include thread_id:
{ "data": null, "metadata": { "filename": "invoice.pdf", "duration": "0.00", "tokens_consumed": 0 } }After — data is always an array (never null), and metadata.thread_id is now present (already the case for the agent-based extractor). A document with no extractable data no longer returns an empty success but 422 no_data_extracted:
{ "data": [{ "invoice_number": "F-2026-001" }], "metadata": { "filename": "invoice.pdf", "duration": 3.45, "tokens_consumed": 1, "thread_id": "01J..." } }Immediate async response (/v2/extractor/async and /v2/extractor/async/:agentReference)
Section titled “Immediate async response (/v2/extractor/async and /v2/extractor/async/:agentReference)”Before
{ "status": "started", "extract_id": "550e8400-e29b-41d4-a716-446655440000", "data": { "message": "Old extraction id is deprecated, use extract_id instead", "extraction_id": "550e8400-e29b-41d4-a716-446655440000" }}After
{ "status": "started", "extract_id": "550e8400-e29b-41d4-a716-446655440000"}The data field (and extraction_id) was leftover cruft from the old identifier — it no longer serves any purpose. If your code read data.extraction_id, switch to extract_id (already the recommended value).
Failure callback and webhook
Section titled “Failure callback and webhook”Applies to the per-request callback and the extraction.completed event on failure.
Before
{ "extract_id": "550e8400-e29b-41d4-a716-446655440000", "error": "Extraction failed: Unable to process the document", "detail": "HTTP status: 500"}After
{ "status": "error", "extract_id": "550e8400-e29b-41d4-a716-446655440000", "code": "agent_timeout", "error": "Agent response timed out", "detail": { "http_status": 504 }}Three changes: an explicit status: "error" field now appears (before, the absence of status: "success" was the only failure signal); code now carries the real machine code for the error (before: always a generic message, no code); detail becomes an object { http_status: <number> } (before: a string "HTTP status: X").
Documentalist
Section titled “Documentalist”POST /v2/documentalist/:agentReference is deprecated (sunset November 30, 2026) and stays an HTTP 200 raw text stream — POST /v2/knowledge/:agentReference takes over, with a standard JSON response and real error HTTP statuses. Both endpoints accept the same input parameters (query, instructions optional, thread_id optional, historic optional).
Before / still on /v2/documentalist — HTTP 200, single-chunk stream response:
Désolé, je ne peux pas vous donner de réponse pour le moment.This same apology text is returned for most internal failures — there is no way to distinguish a real error from a normal response without inspecting the content. Only agent_not_found and thread_not_found short-circuit the stream with a real 404 (raw text body, no JSON envelope).
After / on /v2/knowledge — success:
{ "status": "success", "data": { "answer": "Dimarc is an AI platform specialized in..." }, "metadata": { "thread_id": "01J..." }}After / on /v2/knowledge — errors, with real HTTP statuses:
| Status | Code | Case |
|---|---|---|
| 404 | agent_not_found |
Agent not found, wrong type, private, or outside your organization |
| 404 | thread_not_found |
Provided thread_id does not match a thread for this agent |
| 503 | agent_unavailable |
Processing queue unavailable |
| 504 | agent_timeout |
Agent response timed out |
| 502 | extraction_failed |
The agent could not answer |
GET /v2/token — success is unchanged. 401 now returns an envelope:
Before
(empty body)After
{ "status": "error", "code": "invalid_client", "error": "Invalid client credentials"}Authentication rejections (all protected routes)
Section titled “Authentication rejections (all protected routes)”Before
{ "message": "Unauthorized" }After
| Status | Code | Case |
|---|---|---|
| 401 | unauthorized |
Missing or invalid x-api-key header |
| 403 | forbidden |
API access disabled for your organization |
{ "status": "error", "code": "unauthorized", "error": "Unauthorized" }Error codes
Section titled “Error codes”code is a stable machine identifier, shared by every /v2 endpoint: the full table lives in the API reference.
Support and assistance
Section titled “Support and assistance”For any questions about this migration, contact our support team at contact@dimarc.fr