Batch & IntegrationsBatch & Jobs

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:

PlanItems per batch
Free50
Micro100
Small250
Starter, Growth, Scale500

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 'stmt=@statement.mt940'

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 the jobs:read scope (granted by default).
  • DELETE /v1/jobs/{id} purges a job and its results early; ?purge=true on 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-found 404, 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.