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.
What's in every response.
Data fields, real-world applications, and the companies already running on Amazon Jobs.
- Full Job Descriptions
- Basic & Preferred Qualifications
- Normalized City, State & Country
- Job Category & Business Line
- Schedule Type & Country Code
- Apply URLs & Posting Dates
- 01Labor Market Trend Analysis
- 02Competitive Hiring Intelligence
- 03Location-Based Job Aggregation
- 04Recruiting Dataset Enrichment
How to scrape Amazon Jobs.
Step-by-step guide to extracting jobs from Amazon Jobs-powered career pages—endpoints, authentication, and working code.
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")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 flightimport 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"),
}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 outdef 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"])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.
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.
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.
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.
Always pass an ISO-3 `country` code (e.g. GBR, USA) to scope the market, and record it alongside each job for reproducible extracts.
- 1Page with offset += len(jobs) and stop once the offset reaches the `hits` total.
- 2Parse every `locations` entry as JSON before use — never emit the raw serialized string.
- 3Throttle to a single request at a time with a ~100ms delay to avoid HTTP 429.
- 4Treat job IDs as strings; prefer `id_icims`, then the alphanumeric path id.
- 5Scope results with the ISO-3 `country` param and keep result_limit at 100.
- 6Merge basic and preferred qualifications into the description for the full posting.
One endpoint. All Amazon Jobs jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=amazon jobs" \
-H "X-Api-Key: YOUR_KEY"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.
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.