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