The REST API lets your own systems push documents into docuflow-ai, read them back and maintain the reference data they need. All requests and responses use JSON. An interactive OpenAPI (Swagger) console is available at https://app.docuflow-ai.com/swagger.
Base URL#
All endpoints below are relative to this address. Always use HTTPS. Responses use camelCase property names; property names in request bodies are not case-sensitive.
Authentication#
Every request must carry your organization's API credentials in two headers. Copy them from Admin → Organization Management → Initial Setup.
| Header | Value |
|---|---|
X-Api-Key | Your organization's API key. It identifies the organization the request acts on. |
X-Api-Secret | Your organization's API secret. |
curl https://app.docuflow-ai.com/api/tenant \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET"401 Unauthorized.
Endpoints#
| Method | Path | Purpose |
|---|---|---|
| GET | /api/tenant | Organization name and storage path template. |
| GET | /api/content-types | Accepted file types and their ids. |
| POST | /api/documents | Upload a file (Base64). |
| POST | /api/documents/record | Register a file already in organization storage. |
| POST | /api/documents/record/bulk | Register up to 500 stored files at once. |
| GET | /api/documents | The latest version of every document. |
| GET | /api/documents/{id} | One document with all its metadata. |
| GET | /api/documents/{id}/status | Where a document is in processing. |
| DELETE | /api/documents/{id} | Delete a document and its text. |
| POST | /api/documents/merge | Merge several PDFs into one document. |
| GET | /api/documents/{id}/original | Download the stored file. |
| GET | /api/documents/{id}/pdf | Download the document as PDF. |
| GET | /api/documents/{id}/text | The text OCR read out of the file. |
| GET | /api/documents/{id}/json | The structured data extracted from it. |
| POST | /api/documents/{id}/extract | Queue a fresh extraction. |
| PUT | /api/documents/{id}/tags | Replace the document's tags. |
| GET | /api/documents/{id}/versions | Every version of the document. |
| GET | /api/documents/{id}/jobs | AI jobs that ran on the document. |
| GET | /api/documents/duplicates | Documents stored more than once. |
| POST | /api/documents/search | Keyword or semantic search. |
| GET | /api/document-types | The document types and their schemas. |
| GET | /api/document-types/{id} | One document type with its schema. |
| POST | /api/document-types | Add a document type and index it. |
| PUT | /api/document-types/{id} | Change a document type or its schema. |
| DELETE | /api/document-types/{id} | Deactivate a document type. |
| GET | /api/document-types/{id}/fields | Fields its index can be queried on. |
| GET | /api/document-types/{id}/index-analysis | Index health and document counts. |
| POST | /api/document-types/{id}/reindex | Index the type's documents again. |
| POST | /api/document-types/{id}/recreate-index | Rebuild the index from the schema. |
| POST | /api/search/nl | Ask a question of a document type. |
| GET | /api/jobs | The most recent AI jobs. |
| GET | /api/jobs/{requestId} | Status and result of one job. |
| DELETE | /api/jobs/{requestId} | Cancel a job that has not finished. |
| DELETE | /api/jobs?status=queued | Remove every job in one status. |
| GET | /api/jobs/failures | Records held back after repeated failures. |
| POST | /api/jobs/failures/{id}/requeue | Let a failed record be retried. |
| GET | /api/tags | The tags documents can be labelled with. |
| GET | /api/tags/{id} | One tag with its allowed values. |
| POST | /api/tags | Define a new tag. |
| PUT | /api/tags/{id} | Change a tag or its values. |
| DELETE | /api/tags/{id} | Deactivate a tag. |
| GET | /api/tenant/credits | Credit balance and low-credit threshold. |
| GET | /api/tenant/transactions | What credit was spent and bought. |
| GET | /api/link-collection-schedules | Collection schedules for batch runners. |
Get organization#
Returns the organization the credentials belong to. defaultFilePathTemplate is the folder where the organization's files are stored; use it as filePath for the record endpoints.
{
"tenantName": "Contoso Ltd",
"defaultFilePathTemplate": "\\DocuFlowFiles\\Tenant_3e9d7\\Documents"
}List content types#
Returns the active content types. Pass a contentTypeId from this list when you upload; an inactive or unknown id is rejected.
Content types are shared by every organization on the platform, so the list is read-only through the API. Platform administrators maintain it under Admin → System administration → Content Types.
[
{ "contentTypeId": 1, "contentTypeName": ".pdf" },
{ "contentTypeId": 3, "contentTypeName": ".docx" },
{ "contentTypeId": 28, "contentTypeName": ".png" }
]Upload a document#
Stores the file and queues it for processing. The response returns as soon as the file is saved; processing continues in the background.
| Body field | Type | Description |
|---|---|---|
documentName required | string | File name without extension. |
documentExtension | string | Extension including the dot, e.g. .pdf. PDFs are split automatically. |
contentTypeId | integer | Id from /api/content-types. |
documentContent required | string | The file content, Base64 encoded. |
curl -X POST https://app.docuflow-ai.com/api/documents \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d "{
\"documentName\": \"invoice-2026-0042\",
\"documentExtension\": \".pdf\",
\"documentContent\": \"$(base64 -w0 invoice-2026-0042.pdf)\"
}"{
"documentId": "<generated id>",
"documentName": "invoice-2026-0042",
"documentExtension": ".pdf",
"contentTypeId": 1,
"versionNo": 1
}Register a stored file#
For integrations that write files straight into the organization's storage folder (for example the DocuRunner batch runner). No file content is sent; docuflow-ai reads the file from storage and queues it for OCR (PDFs go through splitting first).
filePath is the folder that holds the file, and the file itself must be named
<documentId><documentExtension>, e.g. portal-000123.pdf. Write the file first, then register it.
| Body field | Type | Description |
|---|---|---|
documentId required | string (≤ 30) | Your unique id for the document. Must not exist yet. |
documentName required | string | File name without extension. |
documentExtension | string | Extension including the dot. |
contentTypeId | integer | Id from /api/content-types. |
filePath required | string (≤ 500) | Folder that contains the file, normally based on defaultFilePathTemplate from /api/tenant. |
sourceKey | string (≤ 20) | Identifies the source system or collection the file came from. |
curl -X POST https://app.docuflow-ai.com/api/documents/record \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"documentId": "portal-000123",
"documentName": "statement-august",
"documentExtension": ".pdf",
"filePath": "\\DocuFlowFiles\\Tenant_3e9d7\\Documents",
"sourceKey": "supplier-portal"
}'Returns 201 Created with the same body as an upload, or 409 Conflict if the documentId already exists.
Register stored files in bulk#
Accepts a JSON array of up to 500 record objects (same fields as above) and registers them in a single request. The whole request is rejected if any record is invalid, if ids repeat inside the request, or if any id already exists.
{
"insertedCount": 2,
"documents": [
{ "documentId": "portal-000001", "documentName": "statement-august", "documentExtension": ".pdf", "contentTypeId": null, "versionNo": 1 },
{ "documentId": "portal-000002", "documentName": "statement-september", "documentExtension": ".pdf", "contentTypeId": null, "versionNo": 1 }
]
}{
"errors": [
{ "index": 1, "error": "FilePath is required." }
]
}List documents#
Returns the latest version of every document in your organization. The response is not paged, so for large organizations expect a big payload and cache what you can.
| Field | Type | Description |
|---|---|---|
documentId | string | Id of this document version. |
documentName | string | File name without extension. |
documentExtension | string | Extension including the dot. |
contentTypeId | integer | Id from /api/content-types. |
documentTypeId | string | The document type the AI classified it as, when there is one. |
documentStatusId | integer | Processing status. |
versionNo | integer | Version number; each version is a separate documentId. |
documentSize | integer | File size in bytes. |
pageCount | integer | Pages found during processing; 0 until then. |
sourceKey | string | Source system or collection the file came from. |
parentDocumentId | string | Set on split or merged documents, pointing at the document they came from. |
tags | string | Tags assigned in the app. |
createdDate | string | UTC timestamp of the upload. |
[
{
"documentId": "portal-000123",
"documentName": "statement-august",
"documentExtension": ".pdf",
"contentTypeId": 1,
"documentTypeId": "inv",
"documentStatusId": 30,
"versionNo": 1,
"documentSize": 184320,
"pageCount": 3,
"sourceKey": "supplier-portal",
"parentDocumentId": null,
"tags": null,
"createdDate": "2026-08-31T09:14:22"
}
]Get a document#
Returns one document with the fields above, or 404 Not Found. The id is the one you got back from an
upload or chose yourself when registering a stored file. Metadata only — use Download in the app for
the file itself.
Check processing status#
The cheap way to poll after an upload. isOcrCompleted turns true once the document reaches the
status configured in OcrCompletedStatusId (25 by default), which is the point where its text is searchable.
Poll every few seconds at most; OCR of a large PDF takes minutes.
curl https://app.docuflow-ai.com/api/documents/portal-000123/status \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET"{
"documentId": "portal-000123",
"documentStatusId": 30,
"documentStatusName": "Indexed",
"isOcrCompleted": true
}Delete a document#
Deletes exactly the version you name; other versions of the same document keep their own ids and stay. Returns
204 No Content, or 404 Not Found if the id is unknown.
Merge documents#
Joins two or more PDFs into a single new document, in the order the ids are given. The merged document enters processing like a freshly uploaded PDF and becomes the parent of the documents it was built from, which keep their own ids and stay where they are.
| Body field | Type | Description |
|---|---|---|
documentIds required | string[] | At least 2 ids, at most 500. Every one must be a PDF in your organization. |
documentName required | string | Name for the merged document, without extension. |
curl -X POST https://app.docuflow-ai.com/api/documents/merge \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"documentIds": ["portal-000001", "portal-000002"],
"documentName": "statement-q3"
}'
Returns 201 Created with the new document. 400 when fewer than two ids are given
or any of them is not a PDF, 404 when an id is unknown (the body lists them).
Download the original file#
Streams the stored file back exactly as it was uploaded, with the media type of its extension and a
Content-Disposition filename. 404 when the id is unknown or the file is no longer
stored (Store documents off).
Download a PDF rendition#
The same file converted to PDF — for Word, Excel and PowerPoint documents, for instance. A PDF is returned
as it is. Answers 409 Conflict when the file's type cannot be rendered as PDF at all, so check
for that rather than assuming every document has a rendition.
Get the extracted text#
The plain text OCR read out of the document: {"documentId": "...", "text": "..."}. Answers
404 while OCR has not finished, so poll
/status until
isOcrCompleted is true before asking for it.
Get the extracted JSON#
The structured data the AI extracted, returned as JSON itself rather than as a string holding JSON — parse
the body directly. Its shape follows the
document type the document was classified as. 404
when nothing has been extracted yet.
{
"invoiceNumber": "A-2026-0042",
"invoiceDate": "2026-03-01",
"supplier": { "name": "Northwind Ltd", "vatNumber": "GB123456789" },
"lineItems": [
{ "description": "Consulting", "quantity": 4, "unitPrice": 250.00, "total": 1000.00 }
],
"total": 1000.00
}Re-run extraction#
Queues the document for a fresh extraction, which overwrites the JSON above when it finishes. No body.
Answers 202 Accepted with {"requestId": "..."}; read
/api/jobs/{requestId} for the outcome. 409
when the document has no readable PDF to analyse.
Replace a document's tags#
Sets the document's tags to exactly what you send: a tag left out of the body is cleared. Each tag name must
be one defined under /api/tags, and each value must be one
that tag allows. A tag of type S takes a single value; a tag of type M takes several.
curl -X PUT https://app.docuflow-ai.com/api/documents/portal-000001/tags \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"tags": {
"Department": ["Finance", "Legal"],
"Year": ["2026"]
}
}'
Returns 200 OK with the updated document. 400 names the offending tag when it is
not defined, the value is not allowed, a single-value tag was given several, or a required tag has no value.
List a document's versions#
Every version sharing the document's name and extension, oldest versionNo first. Each version is
its own document with its own id; this is how you find the others from any one of them.
List a document's jobs#
The AI jobs that ran on this document, newest first, in the shape described under jobs.
List duplicate documents#
Documents whose content is byte-for-byte identical to another one's — matched on checksum and size — so you can decide which to keep. Both sides of each pair are listed.
Search documents#
| Body field | Type | Description |
|---|---|---|
query required | string | What to search for. |
mode | string | keyword (the default) matches words; semantic matches meaning. |
size | integer | Hits to return, 1–100. Defaults to 10. |
skip | integer | Hits to skip, for paging. Defaults to 0. |
tags | string[] | Keeps only documents carrying every one of these tag values. |
documentTypeId | string | Keeps only documents of this type. |
highlights maps each matching index field to fragments with the match wrapped in
<em>; semantic hits carry a score but no highlights.
tags and documentTypeId narrow the hits the search returned rather than the search
itself, because neither is held in the search index. A size of 10 can therefore come back with
fewer than 10 documents once a filter is applied — raise size rather than assuming there are no more.
curl -X POST https://app.docuflow-ai.com/api/documents/search \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"query": "overdue invoice",
"mode": "keyword",
"size": 10,
"tags": ["Finance"]
}'[
{
"documentId": "portal-000042",
"documentName": "invoice-2026-0042",
"documentExtension": ".pdf",
"documentTypeId": "invoice0001",
"documentStatusId": 40,
"tags": "Department, Finance",
"score": 8.42,
"highlights": {
"documentContent": ["this <em>invoice</em> is <em>overdue</em> as of"]
},
"createdDate": "2026-03-01T09:12:44Z"
}
]List document types#
The document types defined for your organization. Each one owns a
schema describing the data extracted from documents of that type, and its own search index.
documentTypeJson is left out here — read a single type for it.
[
{
"documentTypeId": "invoice0001",
"documentTypeName": "Invoice",
"industryNames": "Finance, Legal",
"isIndexCreated": true,
"indexName": "cldoc_invoice0001_acme",
"isActive": true,
"documentTypeJson": null
}
]Get a document type#
The same fields plus documentTypeJson, the type's JSON Schema. 404 when the id is unknown.
Create a document type#
| Body field | Type | Description |
|---|---|---|
documentTypeName required | string (≤ 100) | What the type is called, e.g. Invoice. |
documentTypeJson | string | The schema, as JSON. A sample of the data itself is accepted too and is converted into the schema that describes it. |
isActive | boolean | Defaults to true. |
The search index is created from the schema right after the type is, so a type sent without
documentTypeJson is created without an index — add the schema later to get one. Returns
201 Created, or 400 when documentTypeJson is not valid JSON.
Update a document type#
Replaces the whole type, so send every field you want to keep. When the type already has an index, the schema
change is applied to it as part of the call. Sending isActive: false is how a type is retired —
documents already filed under it keep their classification.
{
"documentTypeId": "invoice0001",
"documentTypeName": "Invoice",
"industryNames": "Finance",
"isIndexCreated": true,
"indexName": "cldoc_invoice0001_acme",
"isActive": true,
"documentTypeJson": "{ \"$schema\": \"...\", \"type\": \"object\", ... }"
}Delete a document type#
Deactivates the type rather than erasing it — the same as sending isActive: false. Its index is left
in place, so documents already filed under it keep their classification and stay searchable. Only your
organization's own types can be deleted. Returns 204 No Content, or 404 Not Found when
the id is not one of yours.
List a document type's index fields#
The fields the type's index holds, each with its search type: [{"name": "invoice.total", "type": "double"}].
These are the fields a natural language question
can be answered from. A type with no index yet returns an empty list.
Analyze a document type's index#
The index's health and size alongside how far the type's documents have got: how many are searchable, how many are still waiting and how many are not ready. The place to look when a search returns less than you expect.
{
"indexName": "cldoc_invoice0001_acme",
"indexExists": true,
"isIndexCreated": true,
"indexedDocumentCount": 1284,
"storeSizeBytes": 10485760,
"primaryStoreSizeBytes": 5242880,
"health": "green",
"status": "open",
"primaryShards": 1,
"replicas": 1,
"totalDocuments": 1300,
"searchableDocuments": 1284,
"waitingDocuments": 16,
"notReadyDocuments": 0
}Reindex a document type#
Puts the type's documents back through indexing against the current schema, keeping the index itself. No body.
Use it after a schema change that only added fields. The response carries status,
success, indexName and affectedDocuments; indexing then runs in the
background.
Recreate a document type's index#
Drops the index, builds it again from the current schema and re-indexes the type's documents — the way to apply a removed or re-typed field. Same response as reindex.
Ask a question in natural language#
Asks a question of one document type's index in plain language: the model turns it into a search, runs it, and
answers with the query it built, the ids it matched and a one-line summary. Body is
{"documentTypeId": "...", "question": "..."}.
Answers 202 Accepted with {"requestId": "..."} — the answer is not ready yet. Poll
/api/jobs/{requestId} until status is
Completed; result then holds the JSON below. 409 when the document type
has no index yet.
curl -X POST https://app.docuflow-ai.com/api/search/nl \
-H "X-Api-Key: $DOCUFLOW_API_KEY" \
-H "X-Api-Secret: $DOCUFLOW_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"documentTypeId": "invoice0001",
"question": "which invoices from Northwind are over 1000 EUR?"
}'{
"query": { "bool": { "filter": [ { "term": { "supplier.name": "Northwind Ltd" } } ] } },
"documentIds": ["portal-000042", "portal-000051"],
"summary": "Two Northwind invoices above 1000 EUR."
}List jobs#
Your organization's most recent AI jobs, newest first. limit is 1–500 and defaults to 50;
anything outside that is 400.
| Field | Type | Description |
|---|---|---|
requestId | string | The id the queueing endpoint returned. Use it to read one job. |
jobId | string | The processing engine's own id, for support requests. |
documentIds | string | Comma-separated ids of the documents the job ran over, when it ran over any. |
functionName | string | What kind of job it was, e.g. Search or RecreateJson. |
status | string | queued, Completed or Failed. |
result | string | The answer once it finished; null while it is still queued. Carries the error message on a failure. |
createdDate | string | UTC timestamp of when the job was queued. |
Get a job#
One job by the requestId a queueing endpoint gave you — this is where the answer to a queued
search or extraction turns up. 404 when the id is unknown. Poll every few seconds at most.
{
"requestId": "6f1c2f2e8a614d0f9b3c7a1d5e0b2c48",
"jobId": "rq:job:0c7f1b2a",
"documentIds": "portal-000042",
"functionName": "RecreateJson",
"status": "Completed",
"result": "{ \"invoiceNumber\": \"A-2026-0042\", ... }",
"createdDate": "2026-03-01T09:12:44Z"
}Delete a job#
Cancels a job that has not finished: it drops out of GET /api/jobs, is taken off the processing
queue, and its result is discarded if the model finishes it anyway. Only a pending or
queued job can be deleted — a finished one answers 409 Conflict. Returns
204 No Content, or 404 when the id is not one of your organization's jobs. When the job
could not be taken off the queue, the answer is 200 OK with queueRemoveFailed: 1; the
job is deleted all the same.
Delete jobs by status#
Deletes every one of your organization's jobs in one status — queued,
Completed or Failed; anything else is 400. Queued jobs are also taken off the
processing queue. Returns 200 OK with how many jobs were deleted and how many could not be taken
off the queue.
{
"deleted": 4,
"queueRemoveFailed": 0
}List job failures#
Records that failed their job often enough to be held back from further attempts — a PDF too large for the
model, for instance. recordId is the document id for OCR and split failures.
lastError says what went wrong on the most recent attempt.
[
{
"aiJobFailureId": 17,
"jobType": "OCR",
"recordId": "portal-000091",
"failedCount": 3,
"lastError": "Document exceeds the model context window.",
"createdDate": "2026-02-27T22:04:10Z",
"lastModifiedDate": "2026-03-01T02:15:38Z"
}
]Retry a job failure#
Clears the record's failure history so its job picks it up again with a fresh set of attempts, and puts a
document that was parked as failed back into the status the job reads from. No body. Returns
204 No Content, or 404 when the id is not one of your organization's failures.
List tags#
The tags documents can be labelled with. allowedValues is the list a document's tag value must
come from, and tagType says whether a document may carry one value (S) or several
(M).
[
{
"tagId": "tag0000000000000001",
"tagName": "Department",
"tagType": "M",
"tagDescription": "Owning department",
"isRequired": false,
"allowedValues": ["Finance", "Legal", "Operations"],
"isActive": true
}
]Get a tag#
One tag in the shape above, or 404 Not Found.
Create a tag#
| Body field | Type | Description |
|---|---|---|
tagName required | string (≤ 50) | What the tag is called, e.g. Department. |
tagType required | string | S for a single value per document, M for several. |
validationDetails required | string (≤ 500) | The values the tag allows, one per line. |
tagDescription | string (≤ 200) | Free text shown in the app. |
isRequired | boolean | When true, every tag assignment must give this tag a value. |
isActive | boolean | Defaults to true. |
Returns 201 Created, or 409 Conflict when the name is already taken.
Update a tag#
Replaces the whole tag, so send every field you want to keep. Returns 200 OK,
404 when the id is unknown, or 409 when another tag already has that name.
Sending isActive: false is how a tag is retired: it stops being offered for new assignments
while documents already carrying it are left as they are.
validationDetails keep it. Their next tag update
has to use a value that is still allowed.
Delete a tag#
Deactivates the tag rather than erasing it — the same as sending isActive: false. Documents already
carrying it keep their values. Only your organization's own tags can be deleted. Returns
204 No Content, or 404 Not Found when the id is not one of yours.
Get the credit balance#
What your organization has left to spend. AI work — OCR, extraction, searches — draws on this balance, so it is
worth checking before a large batch. isLowCredit is true once the balance is at or below
lowCreditThreshold, which is the point an automatic top-up is charged when one is set up.
{
"creditBalance": 128.45,
"lowCreditThreshold": 10,
"isLowCredit": false
}List credit transactions#
Every credit movement, newest first. creditTransactionCode is D for credit spent and
C for credit bought or granted; recordId points at what it was for.
from and to are dates (2026-01-01) and both are inclusive of the whole
day. Either may be left out.
[
{
"creditTransactionId": "ct000000000000000001",
"creditTransactionCode": "D",
"creditTransactionType": "OCR",
"creditTransactionAmount": 1.25,
"usdAmount": 0.50,
"description": "OCR of invoice-2026-0042.pdf",
"recordId": "portal-000042",
"createdDate": "2026-03-01T09:13:02Z"
}
]List link collection schedules#
Returns the schedules defined under Link collections, for batch runners that collect documents on a timetable.
[
{
"linkCollectionScheduleId": "nightly0001",
"linkCollectionScheduleName": "Nightly supplier portals",
"linkCollectionScheduleCron": "0 2 * * *",
"isEnabled": true
}
]Errors#
Successful calls answer 200 OK for reads, 201 Created for the endpoints that create something,
202 Accepted for the ones that queue AI work and 204 No Content for the deletes
and the re-queue. Everything else carries a JSON body:
| Status | When |
|---|---|
400 Bad Request | A required field is missing, the content is not valid Base64 or is empty, a content type id is not active, a bulk request is too large or has duplicates, a schema is not valid JSON, a tag or its value is not defined, or a search size / job limit is out of range. The body is {"error": "..."} (bulk validation returns an errors array with the record index). |
401 Unauthorized | Missing or invalid X-Api-Key / X-Api-Secret. |
404 Not Found | The document, content type, document type, tag, job or organization does not exist — including when it belongs to another organization, which is reported the same way. |
409 Conflict | A documentId or tag name is already in use (the body lists the ids), a content type already uses that name, a document cannot be rendered as PDF, or AI work could not be queued. |
202 Accepted have only queued the work. A 202 says the job was
accepted, not that it succeeded — read /api/jobs/{requestId}
for the outcome, where a failure arrives as status: "Failed" rather than as an HTTP error.
Integration tips#
- Check the result in the app: uploaded documents appear on the Documents page under the system user and move through the statuses.
- Avoid duplicates: identical content is detected by checksum and size and handled by
DefaultDuplicateBehaviour. - Large files: Base64 adds about a third to the payload size; for very large or very many files, write them to storage and use the record endpoints.
- No code? Forward email attachments to your capture address instead.