Search, then full details
This recipe is the workhorse pipeline: run a search for summaries, collect the job_key of every hit, batch-fetch the full role objects, and export everything as CSV. One runnable script, using httpx (pip install httpx).
What is the flow?
POST /v1/searchwithmax_pages: 2— synchronous, since 2 ≤ 3 pages. Costs 2 credits.- Collect
job_keyfrom each summary. POST /v1/roles/batchwith those keys — returns 202 with a job. Costs 1 credit per role.- Poll
GET /v1/jobs/{id}untilcompleted. - Download
GET /v1/jobs/{id}/results?format=csv.
If you would rather have the server do steps 1–3 in one call, use POST /v1/search/with-details; this recipe keeps the steps separate so you can filter between search and detail (skipping roles you already have is the main credit saver).
The script
"""Search Indeed postings, then batch-fetch full details and save CSV."""
import sysimport time
import httpx
BASE = "https://api.rolesapi.com"API_KEY = "rk_live_your_key" # read from an env var in real codeHEADERS = {"Authorization": f"Bearer {API_KEY}"}
KEYWORD = "backend engineer"LOCATION = "Austin, TX"
def search_two_pages(client: httpx.Client) -> list[dict]: """POST /v1/search with max_pages=2 -- sync at <=3 pages.""" r = client.post( f"{BASE}/v1/search", json={ "keyword": KEYWORD, "location": LOCATION, "country": "us", "sort": "date", "max_pages": 2, }, ) r.raise_for_status() return r.json()["data"]
def submit_batch(client: httpx.Client, job_keys: list[str]) -> str: """POST /v1/roles/batch -- always async, 202 with a job id.""" r = client.post( f"{BASE}/v1/roles/batch", json={"job_keys": job_keys, "country": "us"}, ) r.raise_for_status() job = r.json()["data"] print(f"Job {job['job_id']} queued") return job["job_id"]
def wait_for_job(client: httpx.Client, job_id: str) -> None: """Poll GET /v1/jobs/{id} until it reaches a terminal state.""" while True: r = client.get(f"{BASE}/v1/jobs/{job_id}") r.raise_for_status() job = r.json()["data"] p = job["progress"] print(f" {job['status']}: {p['done']}/{p['total']}") if job["status"] == "succeeded": return if job["status"] in ("failed", "timed_out", "aborted"): sys.exit(f"Job {job['status']}: {job['error']}") time.sleep(3)
def download_csv(client: httpx.Client, job_id: str, path: str) -> None: """GET /v1/jobs/{id}/results?format=csv -- whole result set, no envelope.""" r = client.get(f"{BASE}/v1/jobs/{job_id}/results", params={"format": "csv"}) r.raise_for_status() with open(path, "w", encoding="utf-8") as f: f.write(r.text) print(f"Wrote {path}")
def main() -> None: with httpx.Client(headers=HEADERS, timeout=30) as client: summaries = search_two_pages(client) print(f"Search returned {len(summaries)} roles")
job_keys = [s["job_key"] for s in summaries] if not job_keys: sys.exit("No results for this query.")
job_id = submit_batch(client, job_keys) wait_for_job(client, job_id) download_csv(client, job_id, "roles.csv")
if __name__ == "__main__": main()What does the output look like?
roles.csv has one row per role, with nested fields flattened to dot-path columns and arrays joined with | — the rules in Output formats:
job_key,title,company.name,company.rating,location,remote,employment_type,salary.min,salary.max,salary.currency,salary.period,benefits,posted_at,countrya1b2c3d4e5f60718,Senior Backend Engineer,Acme Logistics,4.1,"Austin, TX",false,Full-time,140000,175000,USD,year,Health insurance|401(k) matching|Paid time off,2026-06-28,usHow many credits did this use?
2 (search pages) + 1 per role fetched in the batch. If the search returned 30 roles: 2 + 30 = 32 credits.
How can I adapt it?
- Dedupe before the batch. Filter
job_keysagainst roles already in your database — that line is where this pipeline beatswith-detailson cost. - Skip the poll loop. Register a webhook with
POST /v1/webhooksand let RolesAPI call you — see the Webhooks guide. - Trim the payload. Add
params={"format": "csv", "fields": "job_key,title,company.name,salary.min"}to the results request to control the columns — see Field projection.
RolesAPI is not affiliated with, endorsed by, or sponsored by Indeed, Inc.
Indeed is a registered trademark of Indeed, Inc. RolesAPI provides access to
publicly available data via a stable REST API.