> ## Documentation Index
> Fetch the complete documentation index at: https://developers.mypilotindex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish a job for the partner's company

> Publishes a job for the company MyPilotIndex bound the partner to. The company comes from the binding, never from the body: company_id, source, featured, recruiter_id, created_by and every other member outside the schema are 400 invalid_request. status is active, unless the body says draft, which nobody reads until a change makes it active. A value the catalog does not hold, such as an unknown position_type, is 400 invalid_request, and nothing is published. 201 for a new job; 200 with replayed true for a retry of the same client_ref and body, which publishes nothing. See the Jobs tag for the client_ref and the allowance of 50 jobs per partner per rolling 24 hours. The token must carry jobs.write, otherwise 403 scope_missing.



## OpenAPI

````yaml /api-reference/partner.yaml post /v1/partner/jobs
openapi: 3.1.0
info:
  title: MyPilotIndex Partner API
  version: 1.0.0
  description: >-
    The API of approved partner applications, such as logbooks, that act for a
    pilot who has granted them access on the MyPilotIndex consent screen. It is
    a separate document from the member API (openapi.yaml): partner routes live
    under /v1/partner/ and are never served to browsers. Authenticate with an
    access token from the identity provider's token endpoint
    (grant_type=client_credentials, scopes partner/<scope>) in the Authorization
    header. Every authenticated answer carries X-RateLimit-Limit and
    X-RateLimit-Remaining, the partner's daily allowance and what is left of it
    (a 401, and a path or method outside this document, carry neither); past the
    allowance the API answers 429 rate_limited with Retry-After. Errors use the
    shared envelope below. The API is additive within v1: new fields and routes
    may appear, existing ones do not change meaning.
servers:
  - url: https://{devApiHost}
    description: >-
      Sandbox on the AWS dev environment, with synthetic pilots. The host is
      issued to the partner with its sandbox credential.
    variables:
      devApiHost:
        default: dev-api.mypilotindex.com
        description: The dev API host given to the partner with the sandbox credential.
security: []
tags:
  - name: Grants
    description: >-
      A pilot connects on MyPilotIndex: the partner sends his browser to the
      consent page
      (https://mypilotindex.com/connect/{slug}?client_id&redirect_uri&scope&state&code_challenge&code_challenge_method=S256),
      the pilot signs in and allows, and the browser returns to the registered
      redirect_uri with code and state (or error=access_denied and state when he
      denies). The partner exchanges the code here for a grant and the pilot's
      pairwise id, which signs the pilot in ("Sign in with MyPilotIndex") and
      names him on every later call. A grant that is revoked, unknown or another
      partner's answers 404 on every route, identically.
  - name: Certificates
    description: >-
      Check a certificate a pilot shows you, such as his MPI Report, by its
      public id. The answer is the public verification's, and the call needs no
      grant.
  - name: Webhooks
    description: >-
      MyPilotIndex tells a partner about its grants by webhook: grant.revoked
      when a grant ends, and ping when the partner asks for a test. The webhook
      URL (https only) and its signing secret are set by MyPilotIndex when the
      partner is approved; the secret is shown once. Each event is a POST of the
      event as JSON (Content-Type application/json) signed in the Standard
      Webhooks scheme: webhook-id (the event id), webhook-timestamp and
      webhook-signature, "v1," and the base64 HMAC-SHA256 of
      "<webhook-id>.<webhook-timestamp>.<body>" under the key the secret carries
      in base64 after its whsec_ prefix, over the exact bytes received. Verify
      the signature, refuse a timestamp more than five minutes from your clock,
      dedupe on webhook-id, and answer any 2xx within 10 seconds. A 5xx, a 408,
      a 429, a redirect (never followed), a timeout or a connection failure is
      retried with growing gaps for about a day (twelve attempts), with the same
      webhook-id and a fresh timestamp and signature. A 4xx other than 408 and
      429 is taken as a refusal and not retried, except for a grant.revoked
      event with reason account_deleted: its grant leaves every list with the
      account, so it cannot be reconciled later, and it is retried within the
      same window on any answer but a 2xx, a 4xx included. A partner that missed
      other events reconciles with GET
      /v1/partner/grants?status=revoked&changed_since=.
  - name: Publishing
    description: >-
      Text a partner publishes: a post or a story as a pilot who granted
      feed.write or stories.write, and a post on the organization page the
      partner is bound to, with org.posts. Text only: no media, no YouTube link,
      no hangar, never anonymous. Every create takes a client_ref, 1 to 64 of
      letters, digits and . _ : - , unique per partner and kind: the same ref
      with the same body answers 200 with the stored id and replayed true,
      whatever the allowance says, so a retry after a lost answer is always
      safe; the same ref with another body, or on another grant, is 409
      client_ref_reused. Bodies are compared after normalizing, so surrounding
      spaces do not make two creates. Text may not carry an @handle or @all,
      because a mention notifies people: 422 mentions_not_allowed. A personal
      e-mail address in the text is 400 personal_contact; a company address is
      fine. Rolling allowances count what was published in the last 24 hours,
      removed or not, on top of the daily allowance of every call: 3 posts and 5
      stories per grant, 10 organization posts per partner; past them 429
      publication_throttled with Retry-After. A partner removes only what it
      created, and on a grant only what it created on that grant: anything else
      is the 404 of a row that does not exist, and so is a row the pilot already
      removed. A removal answers 204, and a create it removed still answers its
      id on a retry, with replayed true. A grant whose pilot may not publish
      right now, because his account is suspended or its deletion is pending,
      answers 404 on these routes as a revoked grant does, for as long as that
      lasts, though it stays active in GET /v1/partner/grants. A partner's posts
      and stories look like the pilot's own: no answer of the member API names
      the partner.
  - name: Jobs
    description: >-
      The active listings of MyPilotIndex, and the jobs of the company a partner
      is bound to. jobs.read and jobs.write are the partner's own scopes:
      MyPilotIndex allows them per partner, on the token alone, and no pilot
      sees them. A job goes live at once, without the review queue, as a
      company's job does: the partner is a company MyPilotIndex approved under a
      data agreement. Publishing needs the binding to a company, which
      MyPilotIndex sets; without one, 403 partner_not_bound. Every create takes
      a client_ref, 1 to 64 of letters, digits and . _ : - , unique per partner:
      the same ref with the same body answers 200 with the stored id and
      replayed true, whatever the allowance says, so a retry after a lost answer
      is always safe; the same ref with another body is 409 client_ref_reused.
      Bodies are compared after normalizing, so member order, surrounding spaces
      and the spelling of a number do not make two creates. A rolling allowance
      counts what was published in the last 24 hours, closed or not, on top of
      the daily allowance of every call: 50 jobs per partner; past it 429
      publication_throttled with Retry-After. A partner changes and closes only
      what it created, while the job's company is the one it is bound to:
      anything else is the 404 of a row that does not exist. Job text carries no
      mention rule and no contact guard: it does not reach the feed.
  - name: Insights
    description: >-
      The Workforce: what the pilots of MyPilotIndex look like as a market, in
      aggregates. insights.read is the partner's own scope, for the six screens;
      insights.position is a pilot's, for one pilot against his cohort. Every
      figure comes through the assembly the member screens use, so the floors
      are the server's: a cohort under the floor answers status insufficient and
      no section, a cell under its floor is suppressed, and a filter that would
      leave too few pilots is dropped, which filters.broadened says. No answer
      carries a count of pilots: a cohort is a band.
paths:
  /v1/partner/jobs:
    post:
      tags:
        - Jobs
      summary: Publish a job for the partner's company
      description: >-
        Publishes a job for the company MyPilotIndex bound the partner to. The
        company comes from the binding, never from the body: company_id, source,
        featured, recruiter_id, created_by and every other member outside the
        schema are 400 invalid_request. status is active, unless the body says
        draft, which nobody reads until a change makes it active. A value the
        catalog does not hold, such as an unknown position_type, is 400
        invalid_request, and nothing is published. 201 for a new job; 200 with
        replayed true for a retry of the same client_ref and body, which
        publishes nothing. See the Jobs tag for the client_ref and the allowance
        of 50 jobs per partner per rolling 24 hours. The token must carry
        jobs.write, otherwise 403 scope_missing.
      operationId: partners.create_job.v1
      requestBody:
        required: true
        description: Maximum 65536 bytes. Unknown or repeated members are rejected.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobCreateInput'
            example:
              client_ref: requisition-20261107-07
              title: Captain, Citation Latitude
              description: Part 135 operation out of Cleveland. Type rating provided.
              position_type: captain
              employment_type: full_time
              operation_type: part_135
              base_location: Cleveland, OH
              base_airport_code: KCLE
              work_mode: onsite
              min_total_hours: 3000
              min_jet_hours: 500
              required_licenses:
                - ATP
              salary_min: 145000
              salary_max: 180000
              salary_type: annual
              salary_currency: USD
              application_url: https://careers.example.com/jobs/7
              expires_at: '2026-12-07T12:00:00Z'
      responses:
        '200':
          description: A retry of a create already made; nothing was published.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedJob'
              example:
                job_id: 7b2e4f10-5c1a-4d3e-8f60-1a2b3c4d5e6f
                replayed: true
        '201':
          description: The job was published.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishedJob'
              example:
                job_id: 7b2e4f10-5c1a-4d3e-8f60-1a2b3c4d5e6f
                replayed: false
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/JobNotBound'
        '405':
          $ref: '#/components/responses/Error'
        '409':
          $ref: '#/components/responses/ClientRefReused'
        '413':
          $ref: '#/components/responses/Error'
        '429':
          $ref: '#/components/responses/PublicationThrottled'
        '500':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/Error'
        '504':
          $ref: '#/components/responses/Error'
      security:
        - partnerOAuth:
            - partner/jobs.write
components:
  schemas:
    JobCreateInput:
      allOf:
        - $ref: '#/components/schemas/JobInput'
        - type: object
          required:
            - client_ref
            - title
            - position_type
          properties:
            client_ref:
              $ref: '#/components/schemas/ClientRef'
    PublishedJob:
      type: object
      additionalProperties: false
      required:
        - job_id
        - replayed
      properties:
        job_id:
          type: string
          format: uuid
          description: The job; a change and a close name it.
        replayed:
          type: boolean
          description: >-
            True when this client_ref had already made the job and nothing was
            published.
    JobInput:
      type: object
      description: >-
        The members a partner may set. Each is optional here; a create requires
        its own.
      properties:
        title:
          type: string
          minLength: 1
          description: Surrounding whitespace is trimmed.
        description:
          type:
            - string
            - 'null'
        position_type:
          $ref: '#/components/schemas/JobPositionType'
        employment_type:
          $ref: '#/components/schemas/JobEmploymentType'
        operation_type:
          $ref: '#/components/schemas/JobOperationType'
        base_location:
          type:
            - string
            - 'null'
        base_airport_code:
          type:
            - string
            - 'null'
        work_mode:
          type:
            - string
            - 'null'
          enum:
            - onsite
            - hybrid
            - remote
            - null
        min_total_hours:
          type:
            - integer
            - 'null'
          minimum: 0
        min_pic_hours:
          type:
            - integer
            - 'null'
          minimum: 0
        min_multi_engine_hours:
          type:
            - integer
            - 'null'
          minimum: 0
        min_turbine_hours:
          type:
            - integer
            - 'null'
          minimum: 0
        min_jet_hours:
          type:
            - integer
            - 'null'
          minimum: 0
        required_licenses:
          type: array
          description: License codes, such as ATP or CPL.
          items:
            type: string
            minLength: 1
        required_type_ratings:
          type: array
          description: FAA type rating designators, such as CE-680.
          items:
            type: string
            minLength: 1
        salary_min:
          type:
            - number
            - 'null'
          exclusiveMinimum: 0
        salary_max:
          type:
            - number
            - 'null'
          exclusiveMinimum: 0
          description: At least salary_min.
        salary_type:
          type:
            - string
            - 'null'
          enum:
            - annual
            - hourly
            - per_diem
            - daily_rate
            - monthly
            - fixed
            - null
        salary_currency:
          type: string
          pattern: ^[A-Z]{3}$
        application_url:
          type:
            - string
            - 'null'
          description: An absolute http or https URL.
        application_email:
          type:
            - string
            - 'null'
        application_instructions:
          type:
            - string
            - 'null'
        expires_at:
          type: string
          format: date-time
          description: RFC 3339. 30 days from the create unless it says otherwise.
        status:
          type: string
          enum:
            - draft
            - active
          default: active
    ClientRef:
      type: string
      minLength: 1
      maxLength: 64
      pattern: ^[A-Za-z0-9._:-]{1,64}$
      description: >-
        The partner's own name for this create, unique per partner and kind of
        publication. Send the same ref to retry.
    Error:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
            - correlation_id
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - request_too_large
                - unauthenticated
                - not_found
                - conflict
                - unprocessable
                - operation_disabled
                - rate_limited
                - forbidden
                - dependency_unavailable
                - internal
                - deadline_exceeded
                - method_not_allowed
            message:
              type: string
              description: Stable safe English message, never the underlying error text.
            correlation_id:
              type: string
              pattern: ^[a-f0-9]{32}$
            reason:
              type: string
              description: Optional refusal reason from the operation's closed vocabulary.
    PartnerNotBoundError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - scope_missing
                    - partner_not_bound
    ClientRefReusedError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - client_ref_reused
    PublicationRateLimitedError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - rate_limited
                    - publication_throttled
    JobPositionType:
      type: string
      enum:
        - captain
        - first_officer
        - check_airman
        - instructor
        - management
        - other
        - program
    JobEmploymentType:
      type: string
      enum:
        - full_time
        - part_time
        - contract
        - temporary
        - seasonal
    JobOperationType:
      type: string
      enum:
        - part_121
        - part_135
        - part_91
        - part_91k
        - military
        - government
        - other
  headers:
    RateLimitLimit:
      description: >-
        The partner's daily allowance of calls (1,000 unless agreed otherwise),
        per UTC day.
      schema:
        type: integer
    RateLimitRemaining:
      description: What is left of the allowance after this call.
      schema:
        type: integer
  responses:
    Error:
      description: The shared error envelope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    JobNotBound:
      description: >-
        forbidden. With reason scope_missing, the token does not carry
        jobs.write. With reason partner_not_bound, the partner holds no binding
        to a company; MyPilotIndex sets it.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PartnerNotBoundError'
          example:
            error:
              code: forbidden
              message: Operation not permitted.
              correlation_id: 0af7651916cd43dd8448eb211c80319c
              reason: partner_not_bound
    ClientRefReused:
      description: >-
        conflict with reason client_ref_reused; the ref named an earlier create
        with another body or on another grant, and that create stands. Use a new
        ref for a new create.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ClientRefReusedError'
          example:
            error:
              code: conflict
              message: Request conflicts with an earlier operation.
              correlation_id: 0af7651916cd43dd8448eb211c80319c
              reason: client_ref_reused
    PublicationThrottled:
      description: >-
        rate_limited. With reason publication_throttled, the rolling allowance
        of this kind is spent and Retry-After names when the next one is taken;
        with reason rate_limited, the partner's daily allowance is used up and
        Retry-After names the next UTC midnight. Nothing was published.
      headers:
        Retry-After:
          description: The HTTP date, in UTC, from which the call is taken again.
          schema:
            type: string
            example: Sat, 07 Nov 2026 12:15:00 GMT
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicationRateLimitedError'
          example:
            error:
              code: rate_limited
              message: Too many requests.
              correlation_id: 0af7651916cd43dd8448eb211c80319c
              reason: publication_throttled
  securitySchemes:
    partnerOAuth:
      type: oauth2
      description: >-
        OAuth 2.0 client credentials. Request a token from the token endpoint of
        the identity provider's domain (https://{cognito-domain}/oauth2/token,
        with HTTP Basic client authentication) and send it as a Bearer token.
        Production uses auth.mypilotindex.com. The sandbox uses the dev
        environment's Cognito domain, which is given to the partner with its
        sandbox credential. A token carries only the scopes the partner was
        approved for.
      flows:
        clientCredentials:
          tokenUrl: https://auth.mypilotindex.com/oauth2/token
          scopes:
            partner/profile: Read the pilot's name, photo and username.
            partner/score: Read the pilot's MPI Score and phase.
            partner/hours.read: Read the pilot's flight hours.
            partner/hours.write: Update the pilot's flight hours with the logbook's totals.
            partner/licenses: Read the pilot's licenses, ratings and type ratings.
            partner/applications: Read the pilot's applications to jobs from the partner's company.
            partner/insights.position: Read how the pilot compares with his cohort.
            partner/jobs.match: Read the active jobs that fit the pilot best.
            partner/feed.write: Publish text posts as the pilot.
            partner/stories.write: Publish text stories as the pilot.
            partner/jobs.read: >-
              Read active job listings. MyPilotIndex allows it per partner,
              never a pilot.
            partner/jobs.write: >-
              Publish jobs for the partner's company. MyPilotIndex allows it per
              partner, never a pilot.
            partner/org.posts: >-
              Post on the partner's organization page. MyPilotIndex allows it
              per partner, never a pilot.
            partner/insights.read: >-
              Read Workforce screens. MyPilotIndex allows it per partner, never
              a pilot.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.