Async mutations
Every mutation — creating a submission, attaching a file — runs as an asynchronous job. You can either wait a few seconds for the result inline, or accept a job handle and poll for it.
Why mutations are asynchronous
A mutation is accepted, queued, and processed by a background worker. This
keeps the API responsive and lets processing retry safely on transient
failures. Two request headers let you control the experience:
Idempotency-Key (safe retries) and Prefer: wait
(wait inline vs. poll).
Idempotency-Key (optional)
Send an Idempotency-Key header — any unique string you generate,
such as a UUID — to make a mutation safe to retry. If a request is
interrupted (a dropped connection, a timeout) you can resend it with the same
key and be certain you won't create a duplicate.
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
- First use of a key — the request is processed normally.
- Same key, same request body — you get the original job back instead of a new one (a replay), so retries are safe.
- Same key, different body — rejected with
409 conflict(retryable: false): a key must map to exactly one request.
Keys are remembered for 24 hours. The header is optional — omit it and each request creates a new job — but it's strongly recommended for any mutation you might retry.
Wait inline, or poll
By default a mutation returns immediately with a job handle. Add a
Prefer: wait=N header (RFC 7240) to ask the API to hold the
connection for up to N seconds waiting for the job to finish.
Nis clamped to 0–25 seconds.- If the job settles within the window, you get the final result with
200. - If it doesn't settle in time, you get
202 Acceptedwith astatusUrlto poll — the job keeps running.
Worked example: create a submission
Submit a new-business intake and wait up to 15 seconds for the result:
curl -X POST https://api.uat.trailblazertech.com/v1/submissions \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-H "Prefer: wait=15" \
-d '{
"externalRef": "APP-2026-000123",
"submissionType": "application",
"effectiveDate": "2026-09-01",
"productId": "019986b1-d856-781c-b685-79bb74f2007f",
"applicant": { "legalName": "Acme Widgets LLC" }
}'
If it settles in time — 200
{
"submissionId": "01999f7a-2c31-7b90-8a11-6d0e4f2b9c77",
"submissionName": "Acme Widgets LLC",
"status": "submitted"
}
If it's still running — 202
{
"id": "job_01999f7a2c317b90",
"status": "accepted",
"statusUrl": "https://api.uat.trailblazertech.com/v1/jobs/job_01999f7a2c317b90"
}
id is the job handle; statusUrl is the exact URL to
poll next.
Polling a job
Fetch the job by id (or just call its statusUrl):
curl https://api.uat.trailblazertech.com/v1/jobs/job_01999f7a2c317b90 \
-H "Authorization: Bearer $ACCESS_TOKEN"
A job moves through these statuses:
| Status | Meaning |
|---|---|
accepted | Queued, not started yet. |
running | Being processed. |
succeeded | Finished successfully; result is present. |
failed | Finished with an error; error is present. |
processing | See the note below — returned while you poll a not-yet-settled job. |
A settled (succeeded) job
{
"id": "job_01999f7a2c317b90",
"status": "succeeded",
"result": {
"submissionId": "01999f7a-2c31-7b90-8a11-6d0e4f2b9c77",
"submissionName": "Acme Widgets LLC",
"status": "submitted"
},
"createdAt": "2026-09-01T14:03:11.000Z",
"updatedAt": "2026-09-01T14:03:17.000Z"
}
A succeeded job carries result and no
error; a failed job carries error
(code, message, retryable — the same
error envelope) and no result.
The processing status. While a job is still
in flight, GET /v1/jobs/{id} responds with HTTP 200,
a Retry-After: 5 header, and a body of
{ "id": "...", "status": "processing", "retryAfter": 5 }. Treat
processing as "not settled yet — poll again after
retryAfter seconds." (The OpenAPI reference lists only
accepted/running/succeeded/failed
on the job schema; processing is the live polling response and is
documented here.)
You can wait while polling too
GET /v1/jobs/{id} also honors Prefer: wait=N: it
holds the connection up to N seconds and returns the settled
result if the job finishes in that window, otherwise the
processing response. This lets you long-poll instead of tight-looping.
Recommended pattern
- Generate an
Idempotency-Keyand send the mutation withPrefer: wait=15. - On
200, you're done — use theresult. - On
202, pollstatusUrl(optionally withPrefer: wait), honoringRetry-After, until the status issucceededorfailed. - If the connection drops at any point, resend the original request with the
same
Idempotency-Key— you'll get the same job back, never a duplicate.