Skip to Content
Integration

Integration

This guide walks through an integration lifecycle with the external API v2. The API is regional: use your region’s base URL.

RegionBase 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_api feature).

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) and externalRef (the document’s). On retry or re-ingestion, MEA reuses the existing folder (200 instead of 201) 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 sessionPOST /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 chunkPUT /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. FinalizePOST /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 / documents call (503 DOCUMENT_AV_UNAVAILABLE), not while sending chunks. A session belongs to the key that opened it (another key gets 404 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/v1 API is removed: all its endpoints return 410 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}/content to /archives/{archiveId}/documents/{documentId}/content.

The external API reference describes each endpoint, schema and response in detail.