Integration
This guide walks through an integration lifecycle with the external API v2. The API is regional: use your region’s base URL.
| Region | Base URL |
|---|---|
| Europe (EU) | https://api.eu.mearchiver.com/api/external/v2 |
| Americas (US) | https://api.us.mearchiver.com/api/external/v2 |
Type restriction & provenance. A key may be restricted to specific archive types: it only sees and acts within that scope. A key can also only modify or feed the archives it created itself (provenance). Read access is tied to the subscribed plan (
external_apifeature).
Discover allowed types (and their natures)
Before any deposit, list the archive types allowed for your key. Each type
directly includes its valid document natures (natures) — that is where the
mandatory documentNatureId for uploads comes from. Also fetch the type’s
fields (scope external:archives:read):
curl -s "https://api.eu.mearchiver.com/api/external/v2/archive-types" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET"
curl -s "https://api.eu.mearchiver.com/api/external/v2/archive-types/{typeId}/fields" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET"{
"success": true,
"data": [
{
"id": "3a2b1c0d-1111-2222-3333-444455556666",
"label": "Contracts",
"natures": [
{ "id": "7e6d5c40-aaaa-bbbb-cccc-ddddeeee0000", "label": "Signed contract", "is_required": true },
{ "id": "8f7e6d51-bbbb-cccc-dddd-eeeeffff1111", "label": "Annex", "is_required": false }
]
}
]
}The returned field_keys (/fields endpoint) are used to build dynamic_fields;
every is_required field must be provided at deposit time. An empty natures
list means the tenant has not yet linked any nature to that type.
Pagination
GET /archives accepts offset (≥ 0, default 0), limit (1–200, default 50),
search (full-text: folder title + keywords AND the documents’ OCR content),
type (an archive type UUID) and external_ref (resolve by ERP reference).
Search stays limited to the key’s scope (validated folders, allowed types). The
response carries a paged object:
{
"success": true,
"data": { "items": [], "total": 200, "offset": 0, "limit": 50 }
}Iterate by incrementing offset by limit until you cover total. Only
validated and active archives are returned.
Filter by dynamic field: add field.<field_key>=<value> (repeatable), e.g.
?field.year=2026&field.department=Procurement. Exact match, AND semantics.
The field_keys come from /archive-types/{typeId}/fields.
1. Deposit an archive
An archive always has content: there is no bare creation. You deposit the
archive and its first document in a single multipart/form-data request via
POST /archives/with-document (atomic transaction, rolled back if any step
fails → never an empty archive). Scope external:documents:write.
Constraints: archiveTypeId must be one of the allowed types; if the tenant has
a published classification plan, classificationNodeId is required; every
is_required field of the type must appear in dynamicFieldsJson; the file goes
through antivirus (refused 503 if unavailable). Structured fields
(keywords, dynamic_fields) are sent as serialized JSON.
The content is provided either as file (direct multipart, suited to files
up to ~8 MiB) or via stagedUploadId for large files — see
Large files: chunked upload.
curl -s -X POST https://api.eu.mearchiver.com/api/external/v2/archives/with-document \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-F "title=Supplier contracts 2026" \
-F "archiveExternalRef=ERP-FOLDER-7741" \
-F "archiveTypeId=3a2b1c0d-1111-2222-3333-444455556666" \
-F "classificationNodeId=9c8b7a60-aaaa-bbbb-cccc-ddddeeeeffff" \
-F 'dynamicFieldsJson={"year":"2026","department":"Procurement"}' \
-F "documentNatureId=7e6d5c40-aaaa-bbbb-cccc-ddddeeee0000" \
-F "externalRef=ERP-DOC-5512" \
-F "[email protected];type=application/pdf"Idempotency (no duplicates). Provide
archiveExternalRef(your ERP folder reference) andexternalRef(the document’s). On retry or re-ingestion, MEA reuses the existing folder (200instead of201) and deduplicates the document by its reference — never a duplicate. If the folder reference belongs to another key:409 ARCHIVE_EXTERNAL_REF_CONFLICT. You can also resolve a folder by its reference:GET /archives?external_ref=ERP-FOLDER-7741.
{
"success": true,
"data": {
"archive_id": "8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b",
"document_id": "2d7b9f10-aa11-4c22-8d33-44e55f66a778",
"submission_status": "IN_VALIDATION"
},
"message": "Archive and document deposited."
}The archive is auto-submitted to validation if its type has an active
workflow (IN_VALIDATION); otherwise it stays AWAITING_SUBMISSION until an
administrator configures the workflow. A missing required field returns
422 ARCHIVE_REQUIRED_FIELD_MISSING; a missing classification while a plan is
published returns 422 ARCHIVE_CLASSIFICATION_REQUIRED; a disallowed type
returns 403 EXTERNAL_ARCHIVE_TYPE_FORBIDDEN.
2. Add more documents
To add further documents to the same archive, repeat the call below — one
file per request (each file is independently scanned, traced and rate-limited).
Scope external:documents:write. The body is multipart/form-data with required
file and documentNatureId (and optional externalRef). The deposit only
works on an archive created by this key. Every file goes through
antivirus; if the scanner is unavailable, the request is refused
(503 DOCUMENT_AV_UNAVAILABLE).
curl -s -X POST \
"https://api.eu.mearchiver.com/api/external/v2/archives/8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b/documents" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-F "[email protected];type=application/pdf" \
-F "documentNatureId=7e6d5c40-aaaa-bbbb-cccc-ddddeeee0000" \
-F "externalRef=ERP-DOC-5513"For a large file, replace file with stagedUploadId (see next section). If
neither file nor stagedUploadId is provided: 400 DOCUMENT_UPLOAD_NO_FILE.
Documents created here are always unencrypted (is_encrypted=false).
Large files: chunked upload (with resume)
A single multipart request carries a file up to ~8 MiB. Beyond that — and up to
the maximum document size (200 MiB) — deposit the file in chunks via an
upload session, then reference it with stagedUploadId in with-document or
/archives/{archiveId}/documents, in place of the file field. The upload is
resumable: a failed chunk is re-sent on its own, without restarting. Scope
external:documents:write.
1. Open the session — POST /uploads (JSON body):
curl -s -X POST https://api.eu.mearchiver.com/api/external/v2/uploads \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{ "file_name": "large-scan.pdf", "mime_type": "application/pdf" }'{ "success": true, "data": { "upload_id": "f0e1d2c3-4b5a-6978-8c9d-0a1b2c3d4e5f", "chunk_size": 8388608 } }Slice the file into chunk_size-byte pieces (8 MiB).
2. Send each chunk — PUT /uploads/{uploadId}/chunks/{index} (index from
0), body = raw bytes of the slice, header X-Chunk-Sha256 = hex SHA-256 of the
slice (recommended, verified server-side):
curl -s -X PUT \
"https://api.eu.mearchiver.com/api/external/v2/uploads/f0e1d2c3-4b5a-6978-8c9d-0a1b2c3d4e5f/chunks/0" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-H "Content-Type: application/octet-stream" \
-H "X-Chunk-Sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" \
--data-binary @chunk-000.bin{ "success": true, "data": { "state": "PENDING", "chunk_size": 8388608, "received_chunks": [0] } }3. Resume (optional) — GET /uploads/{uploadId} returns received_chunks:
after an interruption, re-send only the missing indexes.
4. Finalize — POST /uploads/{uploadId}/complete (JSON body). The session
moves to STAGED:
curl -s -X POST \
"https://api.eu.mearchiver.com/api/external/v2/uploads/f0e1d2c3-4b5a-6978-8c9d-0a1b2c3d4e5f/complete" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{ "total_chunks": 13, "total_size": 104857600, "sha256": null }'5. Consume — provide stagedUploadId instead of file at deposit:
curl -s -X POST https://api.eu.mearchiver.com/api/external/v2/archives/with-document \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-F "title=Supplier contracts 2026" \
-F "archiveTypeId=3a2b1c0d-1111-2222-3333-444455556666" \
-F "documentNatureId=7e6d5c40-aaaa-bbbb-cccc-ddddeeee0000" \
-F "stagedUploadId=f0e1d2c3-4b5a-6978-8c9d-0a1b2c3d4e5f"Same security as a direct deposit. At consumption time the bytes go through the same checks: antivirus, format/size validation, quota. An infected file or an unavailable scanner is therefore refused at the
with-document/documentscall (503 DOCUMENT_AV_UNAVAILABLE), not while sending chunks. A session belongs to the key that opened it (another key gets404 UPLOAD_NOT_FOUND), expires after 48 h, and each key is capped at 50 active sessions (429 RATE_LIMIT_EXCEEDED). Codes:UPLOAD_NOT_FOUND(404),UPLOAD_CHUNK_INVALID(422),UPLOAD_INVALID_STATE(409).
3. Read a document
Documents are nested under their archive. Metadata
(scope external:documents:read):
curl -s \
"https://api.eu.mearchiver.com/api/external/v2/archives/8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b/documents/2d7b9f10-aa11-4c22-8d33-44e55f66a778" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET"Binary content:
curl -s -L -o contract.pdf \
"https://api.eu.mearchiver.com/api/external/v2/archives/8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b/documents/2d7b9f10-aa11-4c22-8d33-44e55f66a778/content" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET"If the document is end-to-end encrypted, the download returns
403 DOCUMENT_E2EE_PROTECTED (see concepts).
4. List and update
List the documents of an archive:
curl -s \
"https://api.eu.mearchiver.com/api/external/v2/archives/8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b/documents" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET"Update an archive created by this key (scope external:archives:write):
curl -s -X PUT \
"https://api.eu.mearchiver.com/api/external/v2/archives/8f1c0e2a-3b4d-4e5f-9a0b-1c2d3e4f5a6b" \
-H "X-Api-Key: mea_ext_a1b2c3_YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{ "title": "Supplier contracts 2026 (revised)" }'Updating an archive owned by another key returns 403 EXTERNAL_ARCHIVE_NOT_OWNED.
v1 → v2 migration. The old
/api/external/v1API is removed: all its endpoints return410 Gone(EXTERNAL_API_V1_GONE). It did not carry the v2 controls (type restriction, provenance, read limited to validated folders). The major path change: document content moves from/documents/{id}/contentto/archives/{archiveId}/documents/{documentId}/content.
The external API reference describes each endpoint, schema and response in detail.