jobopportunitiesapi-org Companies API

The companies API from jobopportunitiesapi-org — 2 operation(s) for companies.

Operations 2

GET /v1/companies Employers whose live roles this API returns #
GET /v1/companies/{slug} One employer #

Work with this as data

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 Specification

jobopportunitiesapi-org-companies-api-openapi.yml Raw ↑
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