Run, watch and read a job
A job is the unit of work behind every request. POST /jobs/{site} hands you its id at once; the rest of this page is what you do with that id.
Lifecycle
A job moves queued, then running, then one of succeeded, failed or cancelled. The three final states never change afterwards.
| Status | Meaning |
|---|---|
queued | Accepted, waiting for a slot. Your key runs up to its concurrent-job limit at a time; the rest wait here. |
running | Records are being written. count climbs and pages can already be read. |
succeeded | Final. The run finished; the records are complete. |
failed | Final. error says why. Records written before the failure stay readable. |
cancelled | Final. You cancelled it, or the service stopped it; error is null for your own cancel. |
{ "ok": true, "job": { "id": "0f0c4a18-7a5e-4d2a-9f4c-6a1b9d0e2c31", "site": "amazon", "status": "running", "count": 412, "createdAt": 1758393600000, "startedAt": 1758393601200, "finishedAt": null, "stats": null, "error": null, "maxItems": 2000 }}Example data. Times are Unix milliseconds; maxItems is the record cap in effect for this job.
Submit
curl
curl -X POST https://api.urakimon.com/jobs/amazon \
-H "x-api-key: $URAKIMON_API_KEY" \
-H "content-type: application/json" \
-d '{"mode":"product","asins":["B0H1KXX5TL","B0F5HPCLGB","B0GG9BY46M"],"country":"US"}'Node
const res = await fetch("https://api.urakimon.com/jobs/amazon", {
method: "POST",
headers: { "x-api-key": process.env.URAKIMON_API_KEY, "content-type": "application/json" },
body: JSON.stringify({"mode":"product","asins":["B0H1KXX5TL","B0F5HPCLGB","B0GG9BY46M"],"country":"US"}),
});
const { job } = await res.json();
console.log(job.id, job.status);Python
import os
import requests
res = requests.post(
"https://api.urakimon.com/jobs/amazon",
headers={"x-api-key": os.environ["URAKIMON_API_KEY"]},
json={"mode":"product","asins":["B0H1KXX5TL","B0F5HPCLGB","B0GG9BY46M"],"country":"US"},
)
job = res.json()["job"]
print(job["id"], job["status"])Poll
GET /jobs/{id} returns the same object. Poll it until status is final. Every poll counts against your per-minute request limit, so poll every few seconds rather than in a tight loop, or add webhookUrl to the submission and be called instead (see Webhooks).
curl https://api.urakimon.com/jobs/$JOB_ID \
-H "x-api-key: $URAKIMON_API_KEY"Read records in pages
GET /jobs/{id}/records?offset=0&limit=1000 returns one page: records, the offset it started at, total (records written so far) and done (true once the status is final). limit defaults to 1,000 and is clamped to 5,000; offset below 0 reads as 0. You can page while the job is still running; total keeps climbing until done is true.
Node
const headers = { "x-api-key": process.env.URAKIMON_API_KEY };
const all = [];
let offset = 0;
for (;;) {
const url = `https://api.urakimon.com/jobs/${jobId}/records?offset=${offset}&limit=1000`;
const page = await (await fetch(url, { headers })).json();
all.push(...page.records);
offset += page.records.length;
if (page.done && offset >= page.total) break;
}Python
import os
import requests
headers = {"x-api-key": os.environ["URAKIMON_API_KEY"]}
records, offset = [], 0
while True:
page = requests.get(
f"https://api.urakimon.com/jobs/{job_id}/records",
headers=headers,
params={"offset": offset, "limit": 1000},
).json()
records += page["records"]
offset += len(page["records"])
if page["done"] and offset >= page["total"]:
breakStream as NDJSON
Add format=ndjson and the whole set streams back as application/x-ndjson, one JSON record per line, without paging. Best used once done is true so the stream is complete.
curl "https://api.urakimon.com/jobs/$JOB_ID/records?format=ndjson" \
-H "x-api-key: $URAKIMON_API_KEY" > records.ndjsonIf streaming is switched off for maintenance the route answers 400 with NDJSON streaming is off: read GET /jobs/<id>/records in pages; paging keeps working.
List
GET /jobs returns { "ok": true, "jobs": [...] }: every job of the key that is still kept, live or finished, in the same shape as GET /jobs/{id}. A job submitted with another key is not in the list and answers 404 no such job by id.
Cancel
DELETE /jobs/{id} answers 200 with the job. A queued job becomes cancelled at once. A running job stops writing, then settles as cancelled with count at the number of records written by then; those records stay readable until the retention window ends and stay billed. Cancelling a job that is already final changes nothing and returns it as it is.
Retention
A finished job and its records are kept for 24 hours after it settles, then removed; the sweep runs every 15 minutes, so a job can outlive the window by a few minutes. A key also keeps at most 1,000 finished jobs; beyond that the oldest are removed first. A removed job answers 404 no such job, so copy what you need within the window.
When new jobs are refused
A submission can meet a 503. The body says what is happening and the Retry-After header says how many seconds to wait.
| Condition | Status | Retry-After (s) | Message |
|---|---|---|---|
| Maintenance | 503 | 60 | not accepting new jobs right now: retry in 60 seconds |
| A paused source | 503 | 300 | amazon is paused: retry later |
| Capacity exhausted | 503 | 300 | not accepting new jobs right now: capacity is temporarily exhausted, retry in a few minutes |
| A restart in progress | 503 | none | server is draining; retry shortly |
Jobs that were already queued are not failed by a pause: they wait and start on their own when it lifts. The exception is a POST /records request that is waiting on a held job: it is cancelled with the same message as its error and answers 499, because the connection cannot outwait a pause.
Jobs that end early
- A run past the service's duration limit ends
cancelledwitherrorof the formjob exceeded <n>ms; returned partial results. The records written so far are kept and billed. - A run still going when the API restarts cleanly for a release ends
cancelledwithcancelled: the service restarted while this job ran — the records delivered before then are kept (and billed); resubmit to continue. Resubmit the rest with a newidempotency-key. - A run left
runningby an unclean restart (no clean drain) is found at the next boot and endsfailedwithserver restarted while the job was in progress. Submit again with a newidempotency-key. - A run that delivered nothing while at least one request failed ends
failedwithdelivered 0 records; <n> request(s) failed: <reason>. An empty result with no failures is a normalsucceededjob withcount: 0.
Note. A job that was cancelled before it started, for instance because its key was revoked in between, carries a reason beginning cancelled before start: and was never charged. Errors lists them.
Next step
Replace the poll loop with a signed callback: Webhooks.