Batch & Jobs
POST /v1/convert/batch — convert many messages asynchronously (JSON or file upload), poll the job, stream its items as NDJSON or download a zip, or get a signed webhook, with encrypted short-TTL storage.
Async conversion in one call
For volumes beyond the synchronous one-message-at-a-time model, enqueue a batch (up to 16 MiB per request). Each item mirrors the /v1/convert envelope:
curl -sS -X POST https://api.transmute.403fin.io/v1/convert/batch \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"id": "stmt-1", "from": "mt940", "to": "camt.053", "message": "{1:F01AAAADEFF...}"},
{"id": "stmt-2", "from": "mt940", "to": "csv-xero", "message": "{1:F01AAAADEFF...}"}
]
}'
# 202 → {"jobId": "6f1e...", "status": "queued", "itemCount": 2, "expiresAt": "..."}
Requires the convert scope. If the deployment has no job encryption configured, batch routes answer batch-unavailable 503 — the synchronous API is unaffected.
Items per batch
The item limit depends on your plan:
| Plan | Items per batch |
|---|---|
| Free | 50 |
| Micro | 100 |
| Small | 250 |
| Starter, Growth, Scale | 500 |
A larger batch is invalid-input 400 naming your plan's limit — split it. Each message is capped at 2 MiB.
Upload files instead of inlining them
Send a multipart/form-data request with a manifest part (the same envelope, each item naming its file with part instead of carrying message) and one part per file. One file can feed several items:
curl -sS -X POST https://api.transmute.403fin.io/v1/convert/batch \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-F 'manifest={"items":[{"id":"stmt-xml","to":"camt.053","part":"stmt"},{"id":"stmt-csv","to":"csv-xero","part":"stmt"}]};type=application/json' \
-F '[email protected]'
Part names are [A-Za-z0-9._-], up to 64 characters. File names and part content types are ignored — the format is detected from the content. Idempotency-Key is not supported with multipart bodies (the request is refused); use the JSON form when you need idempotent retries.
Poll the job
curl -sS https://api.transmute.403fin.io/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $TRANSMUTE_KEY"
# {"jobId":"...","status":"completed","counts":{...},"items":[{"id":"stmt-1","status":"succeeded","result":"<?xml ...","warnings":[...]}, ...]}
GET /v1/jobs/{id}needs thejobs:readscope (granted by default).DELETE /v1/jobs/{id}purges a job and its results early;?purge=trueon the read returns the results and deletes them in one call.- Job ids are tenant-scoped: another tenant's job id is a plain
not-found404, indistinguishable from a nonexistent one.
Stream the items as NDJSON
For large jobs, read one item per line instead of one large document:
curl -sS https://api.transmute.403fin.io/v1/jobs/$JOB_ID/items \
-H "Authorization: Bearer $TRANSMUTE_KEY"
# {"id":"stmt-1","index":0,"status":"succeeded","result":"<?xml ...","meta":{...}}
# {"id":"stmt-2","index":1,"status":"succeeded","result":"Date,Description,...","meta":{...}}
Lines arrive in request order. If the connection drops, resume with ?after=<last index received>. The job status rides in the X-Transmute-Job-Status header.
Download a zip
curl -sS https://api.transmute.403fin.io/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $TRANSMUTE_KEY" \
-H "Accept: application/zip" -o results.zip
One entry per result file, named after the item id with the target's extension (stmt-1.xml, stmt-2.csv; <id>-<n>.<ext> when MT paging split the output), plus index.json with each item's status, warnings and problem. A single synchronous conversion can be zipped the same way with Accept: application/zip on POST /v1/convert.
Reading results — JSON, NDJSON or zip — is not billed; the conversions were metered when the items ran.
Prefer push over polling? Register a webhook and receive a signed job.completed (or job.failed) event.
How batch storage works (the privacy story)
Batch is one of the two places transmute holds message data for a limited time (the other is the idempotency replay cache), and it's engineered to minimize that:
- Item inputs and results are stored as AES-256-GCM-encrypted rows in Postgres, never as files.
- Inputs are deleted the moment an item completes; only results remain.
- Results are deleted when the job's TTL runs out (default 1 hour, at most 24 hours). The TTL counts from when the job completes, so the worst case is about twice the TTL after upload.
DELETE /v1/jobs/{id}removes a job earlier. - Uploads are read into memory, never written to files. Results you read back (JSON, NDJSON or zip) are decrypted and streamed straight to you.
- The database's point-in-time restore keeps encrypted row versions for its restore window after a job is deleted.
- Webhook bodies carry metadata only, never message content.
Billing
Each item that the engine processes bills like a synchronous conversion (same 2xx/400/422 rules); queued-but-never-processed items don't.