Docs / Job Data API / Endpoints / Batch liveness
Batch liveness
Answers “which of these ids are still live?” for up to 1,000 ids in one request, billed as ONE request rather than one per id. Post `{"ids": [...]}`; every id you send comes back exactly once, in order, with a `state` of `active`, `inactive`, `missing` or `unknown` — an id we could not resolve is answered `unknown` rather than dropped, because a missing row is indistinguishable from a retired one and that ambiguity is the whole reason this endpoint exists. It reads the same path as `GET /v1/jobs/{id}`, so its verdict carries that endpoint's authority: absence from a `?active=true` sweep does not.
Request
curl https://api.softon.dev/v1/jobs/liveness \
-H "Authorization: Bearer $SOFTON_KEY"
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.softon.dev/v1/jobs/liveness", nil) req.Header.Set("Authorization", "Bearer "+os.Getenv("SOFTON_KEY")) res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() var page struct { Data []Livenes `json:"data"` Meta struct{ Next *string `json:"next"` } `json:"meta"` } if err := json.NewDecoder(res.Body).Decode(&page); err != nil { log.Fatal(err) } // page.Meta.Next → send it back as ?cursor= for the following page
import json, os, urllib.parse, urllib.request url = "https://api.softon.dev/v1/jobs/liveness" req = urllib.request.Request(url, headers={ "Authorization": "Bearer " + os.environ["SOFTON_KEY"], }) page = json.load(urllib.request.urlopen(req)) # page["meta"]["next"] → send it back as ?cursor= for the following page
const url = new URL("https://api.softon.dev/v1/jobs/liveness"); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SOFTON_KEY}` }, }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); const { data, meta } = await res.json(); // meta.next → send it back as ?cursor= for the following page
Response
The envelope is identical on every softon.dev API: data, meta, error. Only the shape inside data changes per dataset — see the response envelope.
{
"data": [
{
"data": [
{ "id": "tap_az:48493167", "state": "active", "active": true, "checked_at": "2026-09-15T09:14:02.117Z" },
{ "id": "easyjob_az:2", "state": "inactive", "active": false, "checked_at": "2026-09-15T09:14:02.117Z" },
{ "id": "boss_az:99999", "state": "missing", "active": null, "checked_at": "2026-09-15T09:14:02.117Z" },
{ "id": "not-an-id", "state": "unknown", "active": null, "checked_at": "2026-09-15T09:14:02.117Z" }
],
"meta": { "requested": 4, "answered": 4, "request_id": "req_9f2c1a55d0e3b477" },
"error": null
}
],
"meta": { "count": 1, "next": "eyJvIjoyfQ", "request_id": "req_9Fv3" },
"error": null
}
Fields of data
| Field | Type | Description |
|---|---|---|
id |
string | Echoed back exactly as you sent it, including an id this platform could not parse. Match on it; it is not normalised. |
state |
string | One of `active`, `inactive`, `missing`, `unknown`, and that set is closed. `active` — the posting is here and its source still lists it. `inactive` — it is here and its source's last complete run did not list it. `missing` — this platform holds no such posting, either because it never did or because it was withdrawn on request; both are "gone" to a caller reconciling their own copy. `unknown` — no verdict, because the id could not be resolved into one. Treat `unknown` as "change nothing, ask again"; it is the safe answer and it is why every id gets a row rather than being dropped. |
active |
boolean · nullable | A real boolean whenever `state` is `active` or `inactive`, and null otherwise. Never absent. |
checked_at |
string (RFC 3339) | When this verdict was read. One timestamp for the whole batch, because it is one query. |
Try it
Send the request to see a response.