Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/jobopportunitiesapi-org:jobopportunitiesapi-org-companies-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
description: 'Every listing here comes from an employer ATS, a company''s own careers page,
a public employment agency, or a direct submission. Redistribution is
enforced by a database JOIN, not a filter someone can forget.
Three things this API does that a job feed normally does not:
* **It says which fields it guessed.** Every job carries `field_sources`,
naming each field as `published` (carried by the source), `inferred`
(derived by us) or `absent`. 88.0% of live listings have an *inferred*
remote status; 11.3% have one the source actually stated. Filter with
`remote_confirmed=true` to get only the latter.
* **It says when each row was last verified.** `last_verified_at` is when we
last confirmed the vacancy still exists at its source, not when it was
posted. `GET /v1/meta/freshness` publishes the coverage numbers.
* **It keeps what closed.** `GET /v1/jobs/closed` returns roles that have
come off their source, with the reason and the date. A req that opened
and closed in nine days is a hiring signal, not a dead link. The date is
the day JOA''s daily verification confirmed the advert had gone; before
late September 2026 that verification ran in large catch-up batches, so
earlier closure dates show when JOA confirmed a closure, not when the
employer closed the role.
Changelog 2026-10-07: new contract fields `engagement`,
`contract_category`, `agency` and `working_schedule`, each with
`field_sources` provenance and filters (`?engagement=`,
`?contract_category=`, `?agency=`, `?working_schedule=`; absence token
`unknown`, alias `not_stated`). Values appear only when the posting or the
source states them (or, later, when an audited rule establishes them) —
most postings will show `unknown` at launch, and that is the honest answer,
not a defect. `contract_category=permanent` returns only postings that
STATE permanence. Per-field coverage is published on /public/coverage.
`employment_type` is unchanged and frozen.
Counts in this document are not typed in: they come from the daily
statistics snapshot every page of the website uses (GET /public/site-stats):
2,080,732 live employer-direct listings (employer_direct_live) and
129,261 employers as of 2026-10-20.'
summary: A verified ledger of open jobs, with per-field provenance and closure history.
title: Job Opportunities Companies API
version: '2026-10-07'
servers:
- url: https://api.jobopportunitiesapi.org
security:
- bearerAuth: []
tags:
- name: Companies
paths:
/v1/companies:
get:
description: 'Every company here has at least one live listing you can fetch, so
`open_roles` and `GET /v1/jobs?company=` reconcile. That is the
point of the endpoint: a directory entry advertising roles the API will
not serve is a broken promise.
Further companies (22,138 when measured on 8 Aug 2026) were seen hiring on their own careers page,
under a source we do not redistribute. They carry `own_site_roles` and
`open_roles: 0`, their individual pages resolve normally, and they are
included only with `include_discovered=true` — they are evidence a
company is hiring, not inventory you can buy.
Ordered by `GREATEST(open_roles, own_site_roles) DESC, name, slug`. The
slug makes the ordering total: without it ties could be reordered
between two requests (measured 3 Aug 2026: 18,213 tie groups covering
36,464 of 186,558 companies, 19.5%), so offset paging silently repeated
and skipped rows.
`offset` is capped at 100,000 and an offset beyond that is a **422**, not
a silently clamped 200. Use `cursor` for deep paging — it is lossless and
has no limit.'
parameters:
- in: query
name: limit
schema:
default: 25
maximum: 200
minimum: 1
type: integer
- in: query
name: offset
schema:
maximum: 100000
minimum: 0
type: integer
- description: The next_cursor from the previous page. Overrides offset.
in: query
name: cursor
schema:
type: string
- in: query
name: country
schema:
type: string
- in: query
name: org_type
schema:
type: string
- in: query
name: source_type
schema:
type: string
- description: 'Only companies whose domain we fetched and found their own name on.
An absent website means we could not prove one, not that they have none.
'
in: query
name: has_website
schema:
type: boolean
- description: 'Also return the companies we watched hiring on their own careers page
under a source we do not redistribute. They arrive with
`open_roles: 0` and a non-zero `own_site_roles`, and `careers_url`
points at the page we saw them on. Useful for enrichment; not
inventory.
'
in: query
name: include_discovered
schema:
type: boolean
- in: query
name: q
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyPage'
description: A page of companies.
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/RecordQuotaExhausted'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
summary: Employers whose live roles this API returns
tags:
- Companies
operationId: getV1Companies
x-operation-id-source: derived
/v1/companies/{slug}:
get:
parameters:
- in: path
name: slug
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/Company'
type: object
description: The company.
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/RecordQuotaExhausted'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
summary: One employer
tags:
- Companies
operationId: getV1CompaniesBySlug
x-operation-id-source: derived
components:
responses:
Unprocessable:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'A parameter we will not guess at: an unknown source_type or provider, an unparseable timestamp, a malformed cursor. Note the deliberate exceptions, which are clamps rather than errors: `limit` is capped at the plan maximum (a non-numeric value falls back to the default), and comma-separated lists are truncated to the per-parameter maximum shown on each. Unknown query parameters are ignored.
'
NotFound:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: No such row.
RecordQuotaExhausted:
content:
application/json:
schema:
$ref: '#/components/schemas/LimitError'
description: 'The plan''s monthly RECORD allowance is spent. Distinct from 429: a 429 means slow down and retry, a 402 means the licence is used up until the allowance resets. The monthly window is the CALENDAR month in UTC: it resets on the 1st of the next month at 00:00 UTC, and the body gives the exact instant. Do not retry before then. Every metered response carries `X-RateLimit-Records-Remaining` and `X-RateLimit-Records-Reset` (the reset instant, Unix seconds), so this is visible long before it happens.
'
headers:
X-RateLimit-Records-Limit:
description: Records included in the plan this month.
schema:
type: integer
X-RateLimit-Records-Reset:
description: Unix seconds of the instant the monthly allowance resets (the 1st of next month, 00:00 UTC). There is no Retry-After on a 402.
schema:
type: integer
X-RateLimit-Records-Used:
description: Records consumed this month.
schema:
type: integer
Unauthorized:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Missing, unknown, revoked or expired key. All four are reported identically.
RateLimited:
content:
application/json:
schema:
$ref: '#/components/schemas/LimitError'
description: 'Over your plan''s per-minute or per-day limit. `Retry-After` says how long (seconds to the next minute boundary, or to UTC midnight on a daily breach). The body says exactly when the limit resets (`resets_at`, `resets_at_text`) and, when a higher plan exists, links a same-key upgrade (`upgrade_url`).
'
headers:
Retry-After:
description: Seconds until the limit resets.
schema:
type: integer
X-RateLimit-Reset:
description: Unix seconds of the next UTC midnight, when the daily counter resets.
schema:
type: integer
schemas:
Error:
properties:
docs:
type: string
error:
type: string
message:
type: string
type: object
CompanyPage:
properties:
data:
items:
$ref: '#/components/schemas/Company'
type: array
has_more:
type: boolean
limit:
type: integer
next_cursor:
type:
- string
- 'null'
offset:
type: integer
type: object
Company:
properties:
careers_url:
type: string
country:
type: string
first_seen:
type: string
industry:
type: string
logo:
type: string
name:
type: string
open_roles:
description: Roles retrievable from THIS API. Never a number the API cannot honour.
type: integer
org_type:
type: string
own_site_roles:
description: Vacancies seen on the company own careers page.
type: integer
sectors:
items:
type: string
type: array
slug:
type: string
source_types:
description: '`aggregator` and `agency` are reserved and match no rows today; see the source_type enum.
'
items:
enum:
- ats
- career_site
- public_agency
- aggregator
- agency
type: string
type: array
website:
type: string
website_verified:
description: When we fetched this domain and found the company own name on it.
type: string
type: object
LimitError:
allOf:
- $ref: '#/components/schemas/Error'
- properties:
docs_url:
description: The documentation section on this limit.
format: uri
type: string
limit:
description: The limit that was hit, in the unit of `window` (records per month, requests per day or per minute).
format: int64
type: integer
plan:
description: The key's plan, e.g. `explore`.
type: string
resets_at:
description: The exact reset instant, RFC 3339, always UTC.
example: '2026-11-01T00:00:00Z'
format: date-time
type: string
resets_at_text:
description: The same instant in plain English, UTC.
example: 1 November 2026 at 00:00 UTC
type: string
upgrade_plan:
description: The plan `upgrade_url` moves the key to. `growth` for a free (Explore) key or a live trial (the one-click link); otherwise the next plan up. Absent for the top plan and for marketplace keys.
example: growth
type: string
upgrade_url:
description: 'For a free (Explore) key or a live trial that hit the monthly record limit: a one-click link for the SAME key
(`https://jobopportunitiesapi.org/upgrade?k=<signed token>`). Opening it, no sign-in needed, redirects to a
personal Creem checkout that upgrades this key to Growth monthly; same secret, nothing to change in the integration.
The token is signed, scoped to the key and valid for 48 hours. For any other key it is a same-key checkout link for
`upgrade_plan` (the signed-in owner of the key completes it).
'
format: uri
type: string
used:
description: Usage in that window when the limit was hit.
format: int64
type: integer
window:
description: Which limit was hit. `calendar_month_utc` resets on the 1st of the next month at 00:00 UTC; `day_utc` at the next 00:00 UTC; `minute` at the next minute boundary.
enum:
- calendar_month_utc
- day_utc
- minute
type: string
type: object
description: 'The body of a 402 `record_quota_exhausted` and a 429 `rate_limited`. `error`, `message` and `docs` are the same envelope as every other error; the remaining fields are optional additions. Marketplace channel keys and keys with no higher plan get the reset time but no `upgrade_*` fields.
'
securitySchemes:
bearerAuth:
description: '`Authorization: Bearer YOUR_API_SECRET`'
scheme: bearer
type: http