openapi: 3.1.0
info:
  title: Nomado24 Jobs API
  version: 2.0.0
  summary: Free public API for remote & hybrid jobs in Germany and the EU.
  description: >
    Free, unauthenticated REST API for remote and hybrid job listings across Germany and the EU, aggregated
    and deduplicated from multiple sources. Every result links to its canonical listing on nomado24.de. Free
    to use with an attribution link back to nomado24.de.
  contact:
    name: Nomado24
    url: https://www.nomado24.de/en/developers
    email: anton.petuchow@nomado24.de
  license:
    name: Free to use with attribution to nomado24.de
    url: https://www.nomado24.de/en/developers
servers:
  - url: https://api.nomado24.de
    description: Production
externalDocs:
  description: API documentation
  url: https://www.nomado24.de/en/developers
paths:
  /api/public/v1/jobs:
    get:
      operationId: listJobs
      summary: List remote and hybrid jobs
      description: >
        Returns a paginated list of remote and hybrid job postings. No authentication. Rate limit ~240
        requests per 15 minutes per IP. Open CORS.
      parameters:
        - name: page
          in: query
          description: Page number (1-based).
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: per_page
          in: query
          description: Results per page (clamped to 1..100).
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: q
          in: query
          description: Free-text search across title, company and tags.
          required: false
          schema:
            type: string
        - name: language
          in: query
          description: Filter by detected job-content language.
          required: false
          schema:
            type: string
            enum:
              - de
              - en
              - fr
      responses:
        '200':
          description: A page of jobs.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Job'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '404':
          description: API not enabled (dormant gate; returns 200 in production).
  /api/public/v2/jobs:
    get:
      operationId: listJobsV2
      summary: Search jobs with provenance and structured filters
      description: >-
        Public, unauthenticated API v2. Unknown, unsupported and incompatible parameters produce a problem
        document. Array filters accept comma-separated or repeated values. Follow nextCursor while hasNext is
        true, even when data is empty. snapshotId identifies the query and does not freeze the corpus. Missing
        or withheld values must not be guessed. Rate limit: 240 requests per 15 minutes per IP.
      parameters:
        - name: q
          in: query
          required: false
          description: Search title, company and description.
          schema:
            type: string
            minLength: 1
            maxLength: 200
        - name: country
          in: query
          required: false
          description: Role location country; separate from applicant eligibility.
          schema:
            type: array
            items:
              type: string
              pattern: ^[A-Z]{2}$
          style: form
          explode: false
        - name: applicant_region
          in: query
          required: false
          description: >-
            Applicant location: country code, EU or WORLDWIDE. EEA, DACH and EMEA are rejected. Explicit
            residence restrictions override broad location hints.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
        - name: work_arrangement
          in: query
          required: false
          description: Fully remote or hybrid; mandatory presence can be conditional or infrequent.
          schema:
            type: array
            items:
              type: string
              enum:
                - remote
                - hybrid
          style: form
          explode: false
        - name: employment_type
          in: query
          required: false
          description: Employment forms; unknown selects absence of a determined form.
          schema:
            type: array
            items:
              type: string
              enum:
                - full_time
                - part_time
                - contract
                - freelance
                - temporary
                - internship
                - working_student
                - apprenticeship
                - minijob
                - volunteer
                - other
                - unknown
          style: form
          explode: false
        - name: category
          in: query
          required: false
          description: Not classified. Any non-empty category is rejected with UNSUPPORTED_FILTER.
          schema:
            type: string
        - name: seniority
          in: query
          required: false
          description: Seniority; unknown selects missing classification.
          schema:
            type: array
            items:
              type: string
              enum:
                - student
                - entry
                - junior
                - mid
                - senior
                - lead
                - manager
                - director
                - executive
                - unknown
          style: form
          explode: false
        - name: skills
          in: query
          required: false
          description: Skill slugs.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
        - name: skills_match
          in: query
          required: false
          description: Match any or all requested skills.
          schema:
            type: string
            enum:
              - any
              - all
            default: any
        - name: salary_min
          in: query
          required: false
          description: >-
            Minimum annual EUR bound, matched against normalized salary. Inspect salaryOrigin:
            legal_derivation is not an employer-stated salary.
          schema:
            type: number
            minimum: 0
        - name: salary_max
          in: query
          required: false
          description: Maximum annual EUR bound.
          schema:
            type: number
            minimum: 0
        - name: salary_currency
          in: query
          required: false
          description: Only EUR is supported. Other currencies are rejected.
          schema:
            type: string
            enum:
              - EUR
            default: EUR
        - name: company
          in: query
          required: false
          description: Exact company names, case-insensitive; not verified legal entities.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
        - name: language
          in: query
          required: false
          description: >-
            Required applicant language codes. Unlike v1, this does not filter the language of the posting
            text.
          schema:
            type: array
            items:
              type: string
              pattern: ^[a-z]{2}$
          style: form
          explode: false
        - name: published_after
          in: query
          required: false
          description: Employer/source publication date lower bound.
          schema:
            type: string
            format: date-time
        - name: verified_after
          in: query
          required: false
          description: Lower bound on the verification timestamp, interpreted with verification.method.
          schema:
            type: string
            format: date-time
        - name: source
          in: query
          required: false
          description: Source identifiers.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
        - name: cursor
          in: query
          required: false
          description: Opaque nextCursor. Send only cursor and optionally limit on continuation.
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Results per page. v1 per_page is rejected here.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: sort
          in: query
          required: false
          description: >-
            Supported ordering. relevance is rejected. Salary sort uses persisted salaryMin, not all resolved
            salary tiers.
          schema:
            type: string
            enum:
              - published_desc
              - verified_desc
              - salary_asc
              - salary_desc
            default: published_desc
        - name: include_total
          in: query
          required: false
          description: >-
            Accepted for compatibility; total is null and totalRelation is not_computed because serving-time
            filters apply.
          schema:
            type: string
            enum:
              - none
              - estimated
              - exact
            default: none
      responses:
        '200':
          description: Matching page, including an empty successful page.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobsV2Response'
        '400':
          description: Invalid, unsupported or incompatible filters.
          content:
            application/problem+json:
              schema:
                type: object
                required:
                  - status
                  - code
                  - errors
                properties:
                  status:
                    type: integer
                  code:
                    type: string
                  detail:
                    type: string
                  errors:
                    type: array
                    items:
                      type: object
        '404':
          description: API v2 disabled in this environment.
        '429':
          description: Rate limit exceeded; retry later.
components:
  schemas:
    Job:
      type: object
      required:
        - slug
        - title
        - companyName
        - location
        - remote
        - workArrangement
        - language
        - tags
        - source
        - publishedAt
        - url
      properties:
        slug:
          type: string
        title:
          type: string
        companyName:
          type: string
        location:
          type: string
        remote:
          type: boolean
        workArrangement:
          type: string
          description: e.g. remote, hybrid
        language:
          type: string
          enum:
            - de
            - en
            - fr
        tags:
          type: array
          items:
            type: string
        source:
          type: string
          description: Origin feed, e.g. personio
        publishedAt:
          type: string
          format: date-time
        url:
          type: string
          format: uri
          description: Canonical nomado24.de listing page (never an external apply URL).
        salaryMin:
          type: number
          description: Present only when known.
        salaryMax:
          type: number
          description: Present only when known.
        currency:
          type: string
          description: Present only when salary is known.
    Meta:
      type: object
      required:
        - page
        - perPage
        - count
        - attribution
        - docsUrl
        - license
      properties:
        page:
          type: integer
        perPage:
          type: integer
        count:
          type: integer
        attribution:
          type: string
          example: 'Data: Nomado24 (https://www.nomado24.de)'
        docsUrl:
          type: string
          format: uri
          example: https://www.nomado24.de/en/developers
        license:
          type: string
          example: Free to use with an attribution link to nomado24.de
    JobsV2Response:
      type: object
      required:
        - data
        - pagination
        - generatedAt
        - snapshotId
        - changeWatermark
        - resultStatus
        - attribution
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/JobV2'
        pagination:
          type: object
          required:
            - nextCursor
            - hasNext
            - limit
            - total
            - totalRelation
          properties:
            cursor:
              type:
                - string
                - 'null'
            nextCursor:
              type:
                - string
                - 'null'
            hasNext:
              type: boolean
            limit:
              type: integer
            total:
              type: 'null'
            totalRelation:
              const: not_computed
        generatedAt:
          type: string
          format: date-time
        snapshotId:
          type: string
        changeWatermark:
          type:
            - string
            - 'null'
          format: date-time
        resultStatus:
          enum:
            - ok
            - empty
        attribution:
          type: object
    JobV2:
      type: object
      description: >-
        Canonical job projection with source facts, classifications and legal derivations distinguished by
        dataOrigin. Null or empty fields are unknown or withheld. Salary min/max use salary.period (month, or hour without a supported monthly equivalent).
        normalizedAnnual is null without supported weekly hours; salary sorting puts unknowns last. Cite canonicalUrl and retain verification.method.
      required:
        - id
        - canonicalUrl
        - title
        - company
        - description
        - workArrangement
        - applicantLocations
        - location
        - salary
        - salaryOrigin
        - sourceUrl
        - verification
        - dataOrigin
        - employmentType
        - category
        - seniority
        - skills
        - languages
        - publishedAt
        - firstSeenAt
        - lastVerifiedAt
        - updatedAt
        - expiresAt
        - source
        - status
        - legalInformation
      properties:
        id:
          type: string
        canonicalUrl:
          type: string
          format: uri
        title:
          type: string
        company:
          type:
            - object
            - 'null'
          required:
            - id
            - name
            - slug
            - canonicalUrl
            - logoUrl
          properties:
            id:
              type: string
            name:
              type: string
            slug:
              type: string
            canonicalUrl:
              type: string
              format: uri
            logoUrl:
              type:
                - string
                - 'null'
        description:
          type:
            - object
            - 'null'
          required:
            - html
            - text
            - summary
            - contentLanguage
            - truncated
          properties:
            html:
              type:
                - string
                - 'null'
            text:
              type: string
            summary:
              type:
                - string
                - 'null'
            contentLanguage:
              type: string
            truncated:
              type: boolean
        workArrangement:
          enum:
            - remote
            - hybrid
            - unknown
        applicantLocations:
          type: object
          required:
            - worldwide
            - countries
            - regions
            - rawText
          properties:
            worldwide:
              type: boolean
              description: Broad worldwide eligibility; excludedCountries still take precedence. The unrestricted WORLDWIDE filter requires no exclusions.
            countries:
              type: array
              items:
                type: string
            regions:
              type: array
              items:
                type: string
            excludedCountries:
              type: array
              description: Explicit applicant residence exclusions, overriding any broader region or country hint.
              items:
                type: string
            rawText:
              type:
                - string
                - 'null'
        location:
          type: object
          required:
            - raw
            - countryCode
            - regionCode
            - city
          properties:
            raw:
              type:
                - string
                - 'null'
            countryCode:
              type:
                - string
                - 'null'
            regionCode:
              type:
                - string
                - 'null'
            city:
              type:
                - string
                - 'null'
        salary:
          type:
            - object
            - 'null'
          required:
            - min
            - max
            - currency
            - period
            - gross
            - rawText
            - normalizedAnnual
            - confidence
            - statedPeriod
          properties:
            min:
              type:
                - number
                - 'null'
            max:
              type:
                - number
                - 'null'
            currency:
              const: EUR
            period:
              enum: [month, hour]
            gross:
              type:
                - boolean
                - 'null'
            rawText:
              type:
                - string
                - 'null'
            normalizedAnnual:
              type: object
              required:
                - min
                - max
              properties:
                min:
                  type:
                    - number
                    - 'null'
                max:
                  type:
                    - number
                    - 'null'
            confidence:
              type:
                - number
                - 'null'
            statedPeriod:
              type:
                - string
                - 'null'
        salaryOrigin:
          enum:
            - source_structured
            - nomado24_extracted
            - legal_derivation
            - unknown
        sourceUrl:
          type:
            - string
            - 'null'
        verification:
          type: object
          required:
            - status
            - method
            - checkedAt
          properties:
            status:
              enum:
                - active
                - expired
                - unknown
            method:
              enum:
                - source_http
                - feed_presence
                - unknown
            checkedAt:
              type: string
              format: date-time
        dataOrigin:
          type: object
          required:
            - fields
          properties:
            fields:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/FieldOrigin'
        employmentType:
          type: array
          items:
            type: string
        category:
          type: array
          items:
            type: string
        seniority:
          type: array
          items:
            type: string
        skills:
          type: array
          items:
            type: object
            required:
              - id
              - slug
              - name
              - category
              - confidence
            properties:
              id:
                type: string
              slug:
                type: string
              name:
                type: string
              category:
                type:
                  - string
                  - 'null'
              confidence:
                type:
                  - number
                  - 'null'
        languages:
          type: array
          items:
            type: object
            required:
              - code
              - required
              - level
            properties:
              code:
                type: string
              required:
                type: boolean
              level:
                type:
                  - string
                  - 'null'
        publishedAt:
          type:
            - string
            - 'null'
          format: date-time
        firstSeenAt:
          type: string
          format: date-time
        lastVerifiedAt:
          type: string
          format: date-time
        updatedAt:
          type:
            - string
            - 'null'
          format: date-time
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
        source:
          type: object
          required:
            - id
            - name
            - kind
          properties:
            id:
              type: string
            name:
              type: string
            kind:
              type: string
        status:
          enum:
            - active
            - expired
            - unknown
        legalInformation:
          type: array
          items:
            type: object
            required:
              - key
              - value
              - jurisdiction
              - ruleId
              - ruleVersion
              - effectiveAt
              - explanation
            properties:
              key:
                type: string
              value:
                type: number
              jurisdiction:
                type: string
              ruleId:
                type: string
              ruleVersion:
                type: string
              effectiveAt:
                type: string
              explanation:
                type: string
    FieldOrigin:
      type: object
      required:
        - type
        - sourceId
        - evidenceUrl
        - observedAt
        - method
        - methodVersion
        - confidence
      properties:
        type:
          enum:
            - employer_fact
            - source_fact
            - nomado24_classification
            - legal_derivation
        sourceId:
          type:
            - string
            - 'null'
        evidenceUrl:
          type:
            - string
            - 'null'
        observedAt:
          type: string
          format: date-time
        method:
          type:
            - string
            - 'null'
        methodVersion:
          type:
            - string
            - 'null'
        confidence:
          type:
            - number
            - 'null'
