Dover Jobs API.
Tap a single public JSON feed per Dover-powered careers board to pull every open role, with full HTML descriptions, office locations, and remote flags all arriving inline, no authentication or per-job requests.
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 Dover.
- Full HTML Job Descriptions
- Structured Job Locations
- Remote Work Disposition
- Office Names & Locations
- Requisition & Internal IDs
- First-Published & Updated Dates
- 01Startup Job Aggregation
- 02Remote Role Discovery
- 03Recruiting Market Intelligence
- 04Careers Page Monitoring
How to scrape Dover.
Step-by-step guide to extracting jobs from Dover-powered career pages—endpoints, authentication, and working code.
import re
# Dover boards live at https://app.dover.com/apply/{slug}
APPLY_RE = re.compile(r"app\.dover\.com/apply/([^/?#]+)", re.IGNORECASE)
def tenant_slug(url: str) -> str:
match = APPLY_RE.search(url)
if not match:
raise ValueError(f"Not a Dover apply URL: {url}")
# The feed API is case-sensitive and only resolves the lowercase slug:
# "SOUKMEDIA" 404s, "soukmedia" 200s.
return match.group(1).lower()
slug = tenant_slug("https://app.dover.com/apply/SOUKMEDIA")
print(slug) # "soukmedia"import requests
def fetch_dover_jobs(slug: str) -> list[dict]:
# One public, unauthenticated call returns every open role for the tenant.
url = f"https://app.dover.com/feed/v1/boards/{slug}/jobs"
response = requests.get(url, timeout=30)
if response.status_code == 404:
raise LookupError(f"Company '{slug}' not found on Dover")
response.raise_for_status()
# Response shape: {"jobs": [{"id", "title", "location": {"name"}, ...}]}
return response.json().get("jobs", [])
jobs = fetch_dover_jobs("strattmont")
print(f"Found {len(jobs)} jobs")def parse_listing(slug: str, job: dict) -> dict:
job_id = job["id"] # UUID string
location = (job.get("location") or {}).get("name")
return {
"external_id": job_id,
"title": (job.get("title") or "").strip(),
"location": location,
"remote": job.get("remote"), # "only", "hybrid", or None
# Build the URL from (slug, id) yourself. Dover sometimes rewrites
# absolute_url to a different canonical slug (careerflow -> careerflowai),
# which would break later URL -> company mapping.
"url": f"https://app.dover.com/apply/{slug}/{job_id}",
}
listings = [parse_listing("strattmont", job) for job in jobs]import html
def parse_details(job: dict) -> dict:
# The full description is already inline, so no per-job request is needed.
description = html.unescape(job.get("content") or "")
offices = [o["name"] for o in (job.get("offices") or []) if o.get("name")]
return {
"title": (job.get("title") or "").strip(),
"company_name": job.get("company_name"),
"description_html": description,
"offices": offices,
"requisition_id": job.get("requisition_id"),
"internal_job_id": job.get("internal_job_id"),
"first_published": job.get("first_published"),
"updated_at": job.get("updated_at"),
}
for job in jobs[:5]:
d = parse_details(job)
print(d["title"], "-", d["first_published"])Dover's /feed/v1/boards endpoint is case-sensitive and only resolves the lowercase slug. Lowercase the slug before requesting: 'SOUKMEDIA' 404s while 'soukmedia' returns 200.
Dover sometimes rewrites absolute_url to a different canonical tenant slug (e.g. careerflow to careerflowai). Build the URL yourself from (slug, id) as https://app.dover.com/apply/{slug}/{id} to keep URL-to-company mapping consistent.
The content field is HTML-entity-encoded (&, <, ', —, etc.). Decode it with Python's html.unescape() before storing or displaying.
Aggressive request rates can trigger a 403 block or a 429 rate limit. Keep a short delay (~100ms) between tenants and apply exponential backoff when either status is returned.
These arrays are frequently empty across tenants and remote may be null. Treat every one as optional and guard for missing or empty values rather than indexing directly.
- 1Lowercase the tenant slug before every feed request
- 2Issue one feed request per company and reuse it - full descriptions are already inline
- 3Build job URLs from (slug, id) instead of trusting absolute_url
- 4Decode the content field with html.unescape() before storage
- 5Keep a short delay (~100ms) between tenants and back off on 403/429
- 6Treat remote, offices, and department as optional - they are often empty or null
One endpoint. All Dover jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=dover" \
-H "X-Api-Key: YOUR_KEY" Access Dover
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.