Jobs and polling
Sourcing, research, copy generation, launch, scans and polls take longer than a request should. Each returns a job at once and does the work in the background; you read the job until it settles.
The shape
GET /jobs/{id}
{
"data": {
"id": "…",
"type": "lead_list.build",
"status": "running",
"progress": { "step": "enrich", "done": 31, "total": 50, "message": "…" },
"children_total": 0,
"children_done": 0,
"result": null,
"error": null,
"estimated_cost_usd": 1.9,
"actual_cost_usd": 1.2,
"created_at": "…",
"started_at": "…",
"finished_at": null,
"events": [ { "id": 1, "level": "info", "message": "…", "meta": {}, "created_at": "…" } ]
}
}| Status | Meaning |
|---|---|
queued | Accepted, waiting for a worker. |
running | A worker holds it; progress and events move. |
done | Finished. result holds what it produced and actual_cost_usd what it spent. |
failed | Gave up after its retries. error says why, in plain words. |
cancelled | Stopped on purpose. |
Where the job id comes from
- Sourcing a list returns the list with a
job_id; the list also carries itsjob. - A preview, a launch, a memory scan and an inbox poll return the job itself.
- Copy generation returns a parent job; one child per lead, counted in
children_doneofchildren_total. - The jobs list shows the recent ones for the workspace, newest first.
Waiting well
Poll the job with a growing gap: two seconds, then five, then ten, up to a minute. A list of 50 leads with verification and enrichment takes a few minutes; research of a large company can take ten. Every poll counts against the rate limit, so a tight loop wastes your budget for nothing.
Shell
JOB=…
until [ "$(curl -s https://crm.ashwanth.dev/api/v1/jobs/$JOB -H "Authorization: Bearer $SALES_ENGINE_API_KEY" | jq -r .data.status)" != "running" ]; do sleep 5; doneThe CLI does this for you with sales-engine wait <job id>, and the MCP server exposes it as wait_for_job.
Queueing one by hand
Queue a job by type for the cases the resource endpoints do not cover. The type names are the ones the jobs list shows; the payload is what that type reads.