Runnable, tested integrations for jobopportunitiesapi.org: a ledger of open job vacancies collected directly from employer applicant-tracking systems, employers' own careers pages and public employment agencies — where every field says whether the employer stated it or we inferred it.
Everything here has been run against the live API. run_all.sh runs every
example and fails on any non-2xx; CI runs the keyless half on every push. That
gate exists for a reason, and the reason is in Why this repo has a test
suite.
Python 3.9+, no dependencies — standard library only, so git clone and
python3 examples/... is the whole setup.
git clone https://github.com/lucagiftzek/joa-examples
cd joa-examples
# Needs nothing at all — /public/ is a real, bounded mirror of the same rows
bash examples/00_curl_cookbook.sh
# Free key, no card, no expiry: https://jobopportunitiesapi.org/register
export JOA_KEY=your_key
python3 examples/01_daily_country_pull.py --country DE --category Engineering| what it does | plan | |
|---|---|---|
00_curl_cookbook.sh |
17 requests in plain curl. The first nine need no key at all. | none |
01_daily_country_pull.py |
New roles for a country, daily, incrementally. Cron-ready, resumable, idempotent. | free |
02_watch_a_company.py |
Watch employers by the domain already in your CRM. Prints what opened and what closed, with the closure reason from the API rather than a guess. | free |
03_salary_benchmark.py |
Percentiles from employer-published salaries only. Nothing modelled, and it prints its own denominator. | free |
04_sync_to_spreadsheet.py |
Flatten to CSV with the provenance in columns beside the values. | free |
05_vector_store.py |
Embedding-ready chunks with provenance in the metadata, for RAG that can cite. | free |
06_agent_tool.py |
A tool definition for Claude or GPT, plus the executor — closed vocabularies, compact output, provenance marks. | free |
07_mirror_with_changes.py |
Keep a SQLite mirror in sync from the delta feed. Handles withdrawn, which is the change type that breaks mirrors. |
Growth+ |
Every script takes --help, and every script's docstring explains why it is
written the way it is — which parameter it chose over the obvious one, and what
went wrong when it did the obvious thing first.
Every listing carries a field_sources object:
"field_sources": {
"remote": "published", "salary": "published", "category": "inferred",
"seniority": "inferred", "employment_type": "absent", "location": "published"
}published— the source carried it. An employer or agency wrote it.inferred— we derived it. Ours, not theirs.absent— there is none. Not zero, not false: none.
category and seniority are always inferred; they are read off the job
title by a classifier. remote may be either — ?remote_confirmed=true narrows
to the ones an employer actually stated.
This is the reason to use this API rather than a scrape, and it is the first thing a naive integration throws away. Every example here carries it through to the output: into a CSV column, into vector metadata, into the string a model sees. If your pipeline drops it, your model will state our inference as the employer's claim.
PROMPT.md is a copy-pasteable block that gives a model or a coding
agent everything it needs: the real endpoints, the parameters that actually
exist, the plan gating so it does not propose a 403, the provenance object, the
rate-limit headers and all three of the API's cursors.
| plan | records/month | requests/day | delta feed | bulk export |
|---|---|---|---|---|
| Explore (free, no card) | 1,000 | 5,000 | no | no |
| Growth | 60,000 | 100,000 | yes | no |
| Signal | 400,000 | 400,000 | yes | yes |
| Scale | 2,000,000 | 1,000,000 | yes | yes |
Live matrix: GET /public/plans. Your own: GET /v1/me.
On the free plan /v1/changes, /v1/jobs/expired and /v1/export return 403 —
example 07 exits cleanly and tells you so rather than crashing. /v1/jobs/closed
is available on every plan including free, and it is the free substitute for
the delta feed: it is what example 02 uses to say why a role disappeared.
Records are charged per row returned, so limit is a budget. Every example
prints what it spent.
On 2026-08-16 the first external evaluation this API had ever had followed the documentation page and sent:
?provider=greenhouse,lever&exclude_provider=eures
HTTP 422. There is no provider with the slug eures — it had left
/public/providers the day before, and the documentation had not noticed.
Nothing anywhere was comparing what we published against what worked.
A broken example is worse than no example. It fails in the reader's terminal, it costs them the twenty minutes they had set aside to evaluate you, and it turns every other claim on the page into something they now have to check by hand.
So: run_all.sh runs all of it, .github/workflows/examples.yml runs the
keyless half on every push and every day, and nothing lands here that has not
gone through both.
MIT for the code in this repository. The data returned by the API is governed by the API terms.
- Documentation and plans — https://jobopportunitiesapi.org/api
- Coverage, including the gaps — https://jobopportunitiesapi.org/coverage
- OpenAPI 3.1, no key needed — https://api.jobopportunitiesapi.org/v1/openapi.yaml
- A person reads support@jobopportunitiesapi.org