Jobs
Jobs are the mechanism behind asynchronous operations in the v5 API. Endpoints that kick off long-running work (like watchlist rescreens) return a jobId immediately and run the actual work in the background. Use the jobs endpoint to poll for status.
Overview
When you call an endpoint that starts an asynchronous operation, it doesn't wait for the work to finish. Instead, it:
- Writes a job run record describing what should happen.
- Returns
{ triggered: true, jobId }to the caller — typically within a second. - A background listener picks up the job, runs the work, and writes lifecycle status (running → completed or failed) to the job's result.
To observe what happened, poll GET /v5/verifications/:trace/jobs/:type/:jobId.
Why asynchronous?
Screening and risk cascades fan out to multiple provider calls (one per stakeholder, plus one for the entity). On a large KYB verification this can take tens of seconds — longer than a reasonable HTTP request lifetime. Running them asynchronously means:
- Callers get immediate acknowledgement; no long-held HTTP connections.
- Partial failures (e.g. one stakeholder's Dilisense call times out) don't break the whole operation. Other steps keep running.
- Retries are automatic and bounded — see Retries below.
Job types
| Type | Written by | Runs |
|---|---|---|
watchlist | POST /v5/verifications/:trace/watchlist | Full watchlist screening + risk recalculation cascade |
More types will be added as new asynchronous operations are built. Each type has its own gate, run history, and listener.
Statuses
| Status | Terminal? | Meaning |
|---|---|---|
pending | no | Job has been written but the listener has not started it yet. |
running | no | The listener has claimed the run and is executing the handler. |
completed | yes | Handler finished successfully. The operation's side effects (e.g. new screening documents) are committed. |
failed | yes | Handler threw. See errorSummary for a one-line description. To re-run the operation, dispatch a new job. |
rejected | yes | Another run of the same type was already in-flight when this one tried to claim the gate. See conflictingJobId to find the active run. |
Treat completed, failed, and rejected as stop signals when polling.
Debounce
Each job type has a gate — only one run of that type can be in-flight per verification at a time. If you call a job-dispatching endpoint while a previous run is still running, you'll receive a 429 Too Many Requests with the conflicting job's ID so you can poll that one instead.
Get job status
Retrieve the run record and lifecycle result for a specific job. Use this to poll after a dispatch endpoint returns a jobId.
Path parameters
- Name
trace- Type
- string
- Description
The verification trace that owns this job.
- Name
type- Type
- string
- Description
The job type (e.g.
watchlist).
- Name
jobId- Type
- string
- Description
The job ID returned by the dispatch endpoint.
Response fields
The data field contains two objects: run (the immutable dispatch record) and result (the mutable lifecycle state).
- Name
run.jobId- Type
- string
- Description
Matches the path parameter.
- Name
run.type- Type
- string
- Description
The job type.
- Name
run.payload- Type
- object
- Description
Type-specific input data for the operation. Empty for
watchlist.
- Name
run.requestedAt- Type
- number
- Description
Epoch milliseconds when the job was dispatched.
- Name
run.sourceAction- Type
- string
- Description
One of
manual-api,manual-portal,manual-admin,scheduled,system.
- Name
run.sourceUid- Type
- string
- Description
UID of the user/service that dispatched the job (optional).
- Name
run.sourceEmail- Type
- string
- Description
Email of the operator (optional; present on portal/admin flows).
- Name
result.status- Type
- string
- Description
See the Statuses table.
- Name
result.attemptCount- Type
- number
- Description
Number of listener invocations that have claimed this run.
- Name
result.startedAt- Type
- number
- Description
Epoch milliseconds when the listener claimed the run (set once
running).
- Name
result.completedAt- Type
- number
- Description
Epoch milliseconds when the run reached a terminal status.
- Name
result.errorSummary- Type
- string
- Description
Short human-readable error description (set when
statusisfailed).
- Name
result.conflictingJobId- Type
- string
- Description
The run holding the gate (set when
statusisrejected).
Error responses
| Status Code | Error Type | Description |
|---|---|---|
| 404 | not_found | The job does not exist. |
| 422 | validation_error | Unknown job type or empty jobId. |
Request
curl -X GET "https://api.bronid.com/v5/verifications/{trace}/jobs/watchlist/{jobId}" \
-H "Authorization: Basic {credentials}"
Response — completed
{
"timestamp": "2026-02-28T04:00:05.000Z",
"serviceUid": "5qA5Hq0n1JQTY2TASItxYXSRyND3",
"trace": "abc123-verification-trace",
"path": "/v5/verifications/:trace/jobs/:type/:jobId",
"pathParams": {
"trace": "abc123-verification-trace",
"type": "watchlist",
"jobId": "V1StGXR8_Z5jdHi6B-myT"
},
"statusCode": 200,
"message": "Job retrieved.",
"help": null,
"status": "success",
"data": {
"run": {
"jobId": "V1StGXR8_Z5jdHi6B-myT",
"type": "watchlist",
"payload": {},
"requestedAt": 1740715200000,
"sourceAction": "manual-api"
},
"result": {
"jobId": "V1StGXR8_Z5jdHi6B-myT",
"type": "watchlist",
"status": "completed",
"attemptCount": 1,
"startedAt": 1740715200120,
"completedAt": 1740715203400
}
},
"error": null
}
Response — failed
{
"statusCode": 200,
"status": "success",
"data": {
"run": { "...": "..." },
"result": {
"jobId": "V1StGXR8_Z5jdHi6B-myT",
"type": "watchlist",
"status": "failed",
"attemptCount": 1,
"errorSummary": "Job execution failed",
"completedAt": 1740715230000
}
}
}