Skip to content

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.

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

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.

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.

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.

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).

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").

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" }

code is a stable machine identifier, shared by every /v2 endpoint: the full table lives in the API reference.

For any questions about this migration, contact our support team at contact@dimarc.fr