All platforms

Amazon Jobs Jobs API.

Tap one of the world's highest-volume employers: pull Amazon's corporate and operations openings, with full descriptions, qualifications and normalized locations, straight from the public amazon.jobs JSON search endpoint.

Get API access
Amazon Jobs
Live
<3haverage discovery time
1hrefresh interval
Developer tools

Try the API.

Test Jobs and Feed endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.

What's in every response.

Data fields, real-world applications, and the companies already running on Amazon Jobs.

Data fields
  • Full Job Descriptions
  • Basic & Preferred Qualifications
  • Normalized City, State & Country
  • Job Category & Business Line
  • Schedule Type & Country Code
  • Apply URLs & Posting Dates
Use cases
  1. 01Labor Market Trend Analysis
  2. 02Competitive Hiring Intelligence
  3. 03Location-Based Job Aggregation
  4. 04Recruiting Dataset Enrichment
DIY GUIDE

How to scrape Amazon Jobs.

Step-by-step guide to extracting jobs from Amazon Jobs-powered career pages—endpoints, authentication, and working code.

RESTintermediateUndocumented; throttle to a single request with ~100ms delay to avoid HTTP 429No auth

Call the search.json endpoint

Amazon exposes a public JSON search endpoint at amazon.jobs. Request a page with an offset, a result_limit, and a country filter — the response carries a `hits` total and a `jobs` array.

Step 1: Call the search.json endpoint
import requests

BASE = "https://www.amazon.jobs"

def fetch_page(offset: int, limit: int = 100, country: str = "GBR") -> dict:
    resp = requests.get(
        f"{BASE}/en/search.json",
        params={"offset": offset, "result_limit": limit, "country": country},
        headers={"Accept": "application/json"},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

data = fetch_page(0)
print(data["hits"], "total jobs")

Paginate by offset until you reach hits

Advance the offset by the number of rows actually returned and stop once the offset reaches the advertised `hits` total (or the `jobs` array comes back empty). Add a short delay between calls to stay under rate limits.

Step 2: Paginate by offset until you reach hits
import time

def iter_jobs(country: str = "GBR", limit: int = 100):
    offset = 0
    while True:
        data = fetch_page(offset, limit, country)
        jobs = data.get("jobs", [])
        if not jobs:
            break
        for job in jobs:
            yield job
        offset += len(jobs)
        if offset >= data.get("hits", 0):
            break
        time.sleep(0.1)  # keep a single, throttled request in flight

Build listing URLs and resolve stable IDs

Join each `job_path` onto the amazon.jobs origin to get the canonical listing URL. IDs live in the path (e.g. /jobs/SF260123884) and may be alphanumeric — prefer the `id_icims` value when present, otherwise fall back to the path id.

Step 3: Build listing URLs and resolve stable IDs
import re
from urllib.parse import urljoin

ID_RE = re.compile(r"/jobs/(?P<id>[A-Za-z0-9]+)")

def parse_core(job: dict) -> dict:
    job_path = job.get("job_path") or ""
    listing_url = urljoin("https://www.amazon.jobs", job_path)
    match = ID_RE.search(job_path)
    path_id = match.group("id") if match else None
    external_id = (job.get("id_icims") or path_id or "").strip() or None
    return {
        "external_id": external_id,
        "listing_url": listing_url,
        "title": (job.get("title") or "").strip() or None,
        "company_name": job.get("company_name"),
        "posted_at": job.get("posted_date"),
        "job_category": job.get("job_category"),
        "schedule_type": job.get("job_schedule_type"),
    }

Decode the JSON-encoded locations array

Entries in the `locations` array are serialized JSON strings, not plain city names. Parse each one and read the normalized fields; fall back to the flat `location` field only when the array is empty or malformed.

Step 4: Decode the JSON-encoded locations array
import json

def parse_locations(job: dict) -> list[dict]:
    out = []
    for raw in job.get("locations") or []:
        raw = (raw or "").strip()
        if raw.startswith("{") or raw.startswith("["):
            try:
                payload = json.loads(raw)
            except json.JSONDecodeError:
                continue
            city = payload.get("normalizedCityName") or payload.get("city")
            state = payload.get("normalizedStateName")
            country = payload.get("normalizedCountryName")
            text = ", ".join(p for p in (city, state, country) if p)
            is_remote = (payload.get("type") or "").upper() in ("REMOTE", "VIRTUAL")
            out.append({"text": text, "city": city, "state": state,
                        "country": country, "is_remote": is_remote})
        elif raw:
            out.append({"text": raw})
    if not out and job.get("location"):
        out.append({"text": job["location"]})
    return out

Assemble the full description

The core `description` omits the qualifications. Append the `basic_qualifications` and `preferred_qualifications` HTML blocks so each record carries the complete posting.

Step 5: Assemble the full description
def build_description(job: dict) -> str:
    parts = [job.get("description") or ""]
    if job.get("basic_qualifications"):
        parts.append("<h2>Basic qualifications</h2>" + job["basic_qualifications"])
    if job.get("preferred_qualifications"):
        parts.append("<h2>Preferred qualifications</h2>" + job["preferred_qualifications"])
    return "<br/><br/>".join(p for p in parts if p)

for job in iter_jobs("GBR"):
    record = parse_core(job)
    record["locations"] = parse_locations(job)
    record["description_html"] = build_description(job)
    print(record["external_id"], record["title"])
Common issues
highThe `locations` array contains serialized JSON strings, so treating them as plain text leaks raw JSON like {"normalizedCityName":...} into your dataset.

Detect entries that start with '{', json.loads them, and read normalizedCityName / normalizedStateName / normalizedCountryName. Only fall back to the flat `location` field when the array is empty or fails to parse.

mediumJob IDs are not always numeric — some are alphanumeric (e.g. SF260123884) — so parsing them as integers silently drops rows.

Keep IDs as strings. Extract them with a /jobs/([A-Za-z0-9]+) path regex and prefer the `id_icims` field, which is the stable native identifier when present.

mediumRapid or concurrent requests return HTTP 429 and stall the crawl; 401/403 can also surface when the endpoint throttles aggressively.

Issue one request at a time with a ~100ms delay between pages and back off on 429. Keep result_limit at 100 rather than pushing it higher.

mediumTrusting the `jobs` array when `hits` is 0, or stopping before the offset reaches `hits`, produces partial or internally inconsistent snapshots.

Advance offset by len(jobs) each page, stop when `jobs` is empty, and treat hits==0 with non-empty jobs (or vice versa) as an error rather than a valid result.

lowamazon.jobs is a global board, so omitting or changing the `country` param quietly changes the corpus you pull.

Always pass an ISO-3 `country` code (e.g. GBR, USA) to scope the market, and record it alongside each job for reproducible extracts.

Best practices
  1. 1Page with offset += len(jobs) and stop once the offset reaches the `hits` total.
  2. 2Parse every `locations` entry as JSON before use — never emit the raw serialized string.
  3. 3Throttle to a single request at a time with a ~100ms delay to avoid HTTP 429.
  4. 4Treat job IDs as strings; prefer `id_icims`, then the alphanumeric path id.
  5. 5Scope results with the ISO-3 `country` param and keep result_limit at 100.
  6. 6Merge basic and preferred qualifications into the description for the full posting.
Or skip the complexity

One endpoint. All Amazon Jobs jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://connect.jobo.world/api/jobs?sources=amazon jobs" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

Access Amazon Jobs
job data today.

One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.

99.9%API uptime
<200msAvg response
50M+Jobs processed