Documentation menu

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.

StatusMeaning
queuedAccepted, waiting for a slot. Your key runs up to its concurrent-job limit at a time; the rest wait here.
runningRecords are being written. count climbs and pages can already be read.
succeededFinal. The run finished; the records are complete.
failedFinal. error says why. Records written before the failure stay readable.
cancelledFinal. You cancelled it, or the service stopped it; error is null for your own cancel.
Example job
{  "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
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"]:
        break

Stream 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
curl "https://api.urakimon.com/jobs/$JOB_ID/records?format=ndjson" \
  -H "x-api-key: $URAKIMON_API_KEY" > records.ndjson

If 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.

ConditionStatusRetry-After (s)Message
Maintenance50360not accepting new jobs right now: retry in 60 seconds
A paused source503300amazon is paused: retry later
Capacity exhausted503300not accepting new jobs right now: capacity is temporarily exhausted, retry in a few minutes
A restart in progress503noneserver 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 cancelled with error of the form job 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 cancelled with cancelled: the service restarted while this job ran — the records delivered before then are kept (and billed); resubmit to continue. Resubmit the rest with a new idempotency-key.
  • A run left running by an unclean restart (no clean drain) is found at the next boot and ends failed with server restarted while the job was in progress. Submit again with a new idempotency-key.
  • A run that delivered nothing while at least one request failed ends failed with delivered 0 records; <n> request(s) failed: <reason>. An empty result with no failures is a normal succeeded job with count: 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.