> ## 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.

# Push the pilot's flight totals

> Sends the logbook's current totals for the pilot of a grant that holds hours.write: per aircraft type, in the logbook's own categories and in minutes, and the simulator sessions per device class. It is a whole state, not a delta. MyPilotIndex applies its own aviation rules (SPIC is PIC time, PICUS has its own column, a turboshaft helicopter counts in the helicopter turbine columns and not in airplane turbine, and simulator time never counts as flight time), raises each of the pilot's hour columns to the logbook's value when it is higher and never lowers one, raises the hours in type of the type ratings he holds, and recomputes his MPI Score when anything moved. A type's category is read only when the MyPilotIndex aircraft catalog does not resolve the type, and such a type adds to no engine-type column (jet, turboprop, turbine): a helicopter counts in the helicopter columns and in no engine column; an airplane counts in the airplane total and follows multi_engine in the engine-count columns (single engine, multi engine, PIC multi engine); a glider, balloon, airship, gyroplane or powered lift adds to total time and its role columns only, never to an engine-count column. A type the catalog resolves follows the catalog, whatever its category says. The push carries at least one types row or one simulator row (a logbook of simulator sessions only sends types as an empty array). The grant keeps only aggregates of the push (the resulting columns and counts), never a type designator or a row. as_of is required and names the instant of the logbook's state; it may not be more than 5 minutes past the server's clock, and it is compared to the microsecond. Sending the same as_of and body again (a retry after a lost answer) answers 200 and applies nothing while that push is still the grant's last accepted push; after a newer push the same retry is 409 stale_totals. A retry is recognized by its as_of and by the totals it sums to, not by its bytes: two different bodies sent with one as_of that sum to the same totals are treated as the same push, and the second applies nothing. A push whose as_of is older than the grant's last accepted push is 409 stale_totals; one accepted push per grant per 15 minutes, otherwise 429 push_throttled with Retry-After. 422 hours_rule when the totals, merged with the pilot's record, would break his hours rules (for example a figure above 99,999.9 hours): nothing is applied. Every call counts toward the daily allowance. Pushes take no Idempotency-Key. A grant that is revoked, unknown or another partner grant answers 404 not_found; a grant, or a token, without hours.write is 403 scope_missing.



## OpenAPI

````yaml /api-reference/partner.yaml put /v1/partner/grants/{grant_id}/flight-totals
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=.
paths:
  /v1/partner/grants/{grant_id}/flight-totals:
    put:
      tags:
        - Grants
      summary: Push the pilot's flight totals
      description: >-
        Sends the logbook's current totals for the pilot of a grant that holds
        hours.write: per aircraft type, in the logbook's own categories and in
        minutes, and the simulator sessions per device class. It is a whole
        state, not a delta. MyPilotIndex applies its own aviation rules (SPIC is
        PIC time, PICUS has its own column, a turboshaft helicopter counts in
        the helicopter turbine columns and not in airplane turbine, and
        simulator time never counts as flight time), raises each of the pilot's
        hour columns to the logbook's value when it is higher and never lowers
        one, raises the hours in type of the type ratings he holds, and
        recomputes his MPI Score when anything moved. A type's category is read
        only when the MyPilotIndex aircraft catalog does not resolve the type,
        and such a type adds to no engine-type column (jet, turboprop, turbine):
        a helicopter counts in the helicopter columns and in no engine column;
        an airplane counts in the airplane total and follows multi_engine in the
        engine-count columns (single engine, multi engine, PIC multi engine); a
        glider, balloon, airship, gyroplane or powered lift adds to total time
        and its role columns only, never to an engine-count column. A type the
        catalog resolves follows the catalog, whatever its category says. The
        push carries at least one types row or one simulator row (a logbook of
        simulator sessions only sends types as an empty array). The grant keeps
        only aggregates of the push (the resulting columns and counts), never a
        type designator or a row. as_of is required and names the instant of the
        logbook's state; it may not be more than 5 minutes past the server's
        clock, and it is compared to the microsecond. Sending the same as_of and
        body again (a retry after a lost answer) answers 200 and applies nothing
        while that push is still the grant's last accepted push; after a newer
        push the same retry is 409 stale_totals. A retry is recognized by its
        as_of and by the totals it sums to, not by its bytes: two different
        bodies sent with one as_of that sum to the same totals are treated as
        the same push, and the second applies nothing. A push whose as_of is
        older than the grant's last accepted push is 409 stale_totals; one
        accepted push per grant per 15 minutes, otherwise 429 push_throttled
        with Retry-After. 422 hours_rule when the totals, merged with the
        pilot's record, would break his hours rules (for example a figure above
        99,999.9 hours): nothing is applied. Every call counts toward the daily
        allowance. Pushes take no Idempotency-Key. A grant that is revoked,
        unknown or another partner grant answers 404 not_found; a grant, or a
        token, without hours.write is 403 scope_missing.
      operationId: partners.push_flight_totals.v1
      parameters:
        - name: grant_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        description: >-
          Maximum 262144 bytes (256 KiB). Unknown or repeated members are
          rejected at every level.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FlightTotalsInput'
            example:
              as_of: '2026-11-04T12:00:00Z'
              logbook_authority: ANAC
              types:
                - aircraft_type: B738
                  category: airplane
                  multi_engine: true
                  minutes:
                    total: 61200
                    pic: 0
                    sic: 61200
                    picus: 0
                    spic: 0
                    dual_received: 0
                    solo: 0
                    instructor: 0
                    night: 9000
                    actual_instrument: 4200
                    simulated_instrument: 0
                    cross_country: 58000
                - aircraft_type: R44
                  category: helicopter
                  multi_engine: false
                  minutes:
                    total: 3000
                    pic: 2400
                    dual_received: 600
              simulator:
                - device: FFS
                  minutes:
                    trainee: 480
                    instructor: 0
      responses:
        '200':
          description: >-
            What the push did. A retry of the last accepted push, and a push
            that brings nothing new, answer applied 0 and score_requested false.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PushResult'
              example:
                applied: 9
                unchanged: 3
                dropped: []
                score_requested: true
        '400':
          description: >-
            invalid_request: a malformed body (an unknown or repeated member, a
            value out of range, more than 500 types, no types row and no
            simulator row, one type or one device class twice, an as_of that is
            not RFC 3339 or is more than 5 minutes ahead).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          description: >-
            forbidden with reason scope_missing; the grant or the token does not
            hold hours.write.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScopeMissingError'
        '404':
          $ref: '#/components/responses/Error'
        '405':
          $ref: '#/components/responses/Error'
        '409':
          description: >-
            conflict with reason stale_totals; as_of is older than the as_of of
            the grant's last accepted push. Nothing was applied. Send the
            logbook's current state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StaleTotalsError'
              example:
                error:
                  code: conflict
                  message: Request conflicts with an earlier operation.
                  correlation_id: 0af7651916cd43dd8448eb211c80319c
                  reason: stale_totals
        '413':
          $ref: '#/components/responses/Error'
        '422':
          description: >-
            unprocessable with reason hours_rule; the totals, merged with the
            pilot's record, would break his hours rules. Nothing was applied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HoursRuleError'
              example:
                error:
                  code: unprocessable
                  message: Request cannot be applied.
                  correlation_id: 0af7651916cd43dd8448eb211c80319c
                  reason: hours_rule
        '429':
          description: >-
            rate_limited. With reason push_throttled, the grant's last accepted
            push was less than 15 minutes ago and Retry-After names when the
            next push is taken; with reason rate_limited, the partner's daily
            allowance is used up and Retry-After names the next UTC midnight.
            Nothing was applied.
          headers:
            Retry-After:
              description: The HTTP date, in UTC, from which the call is taken again.
              schema:
                type: string
                example: Wed, 04 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/PushRateLimitedError'
              example:
                error:
                  code: rate_limited
                  message: Too many requests.
                  correlation_id: 0af7651916cd43dd8448eb211c80319c
                  reason: push_throttled
        '500':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/Error'
        '504':
          $ref: '#/components/responses/Error'
      security:
        - partnerOAuth: []
components:
  schemas:
    FlightTotalsInput:
      type: object
      additionalProperties: false
      required:
        - as_of
        - types
      properties:
        as_of:
          type: string
          format: date-time
          description: >-
            The instant of the logbook's state (RFC 3339). Not more than 5
            minutes past the server's clock; compared to the microsecond.
        logbook_authority:
          type: string
          enum:
            - FAA
            - EASA
            - ANAC
            - TCCA
            - CASA
            - other
          description: >-
            Informative; the authority whose rules the logbook keeps. Not
            stored.
        types:
          type: array
          minItems: 0
          maxItems: 500
          description: >-
            One row per aircraft type the logbook knows, each type once. It may
            be empty for a logbook of simulator sessions only: a push carries at
            least one types row or one simulator row (400 invalid_request
            otherwise).
          items:
            $ref: '#/components/schemas/FlightTotalsType'
        simulator:
          type: array
          maxItems: 5
          description: >-
            Sessions in a flight simulation training device, one row per device
            class. Kept apart from flight time.
          items:
            $ref: '#/components/schemas/SimulatorSession'
    PushResult:
      type: object
      additionalProperties: false
      required:
        - applied
        - unchanged
        - dropped
        - score_requested
      properties:
        applied:
          type: integer
          minimum: 0
          description: >-
            How many of the pilot's figures the push raised: hour columns, and
            hours in type of the type ratings he holds.
        unchanged:
          type: integer
          minimum: 0
          description: >-
            How many hour columns of the push the pilot's record already held at
            or above it.
        dropped:
          type: array
          items:
            type: string
          description: >-
            The hour columns left out, sorted, because within the push a column
            was larger than its parent (for example PIC above total time); its
            children are left out with it.
        score_requested:
          type: boolean
          description: >-
            Whether the pilot's MPI Score is recomputed; true exactly when
            applied is above zero.
    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.
    ScopeMissingError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - scope_missing
    StaleTotalsError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - stale_totals
    HoursRuleError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - hours_rule
    PushRateLimitedError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          properties:
            error:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - rate_limited
                    - push_throttled
    FlightTotalsType:
      type: object
      additionalProperties: false
      required:
        - aircraft_type
        - category
        - minutes
      properties:
        aircraft_type:
          type: string
          minLength: 1
          maxLength: 64
          description: >-
            The type as the logbook spells it: the ICAO designator is preferred;
            the MyPilotIndex catalog also resolves FAA designators and aliases.
            Surrounding spaces are ignored and the comparison is case blind.
        category:
          type: string
          enum:
            - airplane
            - helicopter
            - glider
            - balloon
            - airship
            - gyroplane
            - powered_lift
          description: >-
            Read only when the catalog does not resolve the type: a helicopter
            counts in the helicopter columns, an airplane in the airplane total
            and in the engine-count columns multi_engine names, and a glider,
            balloon, airship, gyroplane or powered lift in total time and its
            role columns only. An unresolved type adds to no engine-type column
            (jet, turboprop, turbine).
        multi_engine:
          type:
            - boolean
            - 'null'
          description: >-
            Whether the type is multi engine; null or absent when the logbook
            does not know. Used for the engine-count columns of a type the
            catalog resolves (other than a helicopter) and of an unresolved
            airplane; ignored for every other unresolved category.
        minutes:
          $ref: '#/components/schemas/FlightTotalsMinutes'
    SimulatorSession:
      type: object
      additionalProperties: false
      required:
        - device
        - minutes
      properties:
        device:
          type: string
          enum:
            - FFS
            - FTD
            - FNPT
            - ATD
            - other
          description: >-
            The device class. AATD, BATD, BITD and PCATD are ATD. Trainee time
            in an FFS also counts as full flight simulator time.
        minutes:
          type: object
          additionalProperties: false
          description: >-
            Minutes as a trainee and as an instructor; instructor time is
            accepted and not stored in v1.
          properties:
            trainee:
              $ref: '#/components/schemas/Minutes'
            instructor:
              $ref: '#/components/schemas/Minutes'
    FlightTotalsMinutes:
      type: object
      additionalProperties: false
      description: >-
        The career totals of one type in minutes, each an integer from 0 to
        6,000,000 (100,000 hours); an absent member is 0. spic (student pilot in
        command, solo) counts as PIC time; picus (PIC under supervision) has its
        own column and is never added to PIC.
      properties:
        total:
          $ref: '#/components/schemas/Minutes'
        pic:
          $ref: '#/components/schemas/Minutes'
        sic:
          $ref: '#/components/schemas/Minutes'
        picus:
          $ref: '#/components/schemas/Minutes'
        spic:
          $ref: '#/components/schemas/Minutes'
        dual_received:
          $ref: '#/components/schemas/Minutes'
        solo:
          $ref: '#/components/schemas/Minutes'
        instructor:
          $ref: '#/components/schemas/Minutes'
        night:
          $ref: '#/components/schemas/Minutes'
        actual_instrument:
          $ref: '#/components/schemas/Minutes'
        simulated_instrument:
          $ref: '#/components/schemas/Minutes'
        cross_country:
          $ref: '#/components/schemas/Minutes'
    Minutes:
      type: integer
      minimum: 0
      maximum: 6000000
  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'
  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.

````

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