openapi: 3.1.0
info:
  title: Jet Intelligence Broker API
  version: 1.1.0
  description: Tenant-scoped, read-only access to submitted charter offers and the shared empty-leg collection for the organization bound to the API key.
servers:
  - url: https://www.jet-intelligence.com/api/v1
security:
  - brokerBearer: []
paths:
  /offers:
    get:
      operationId: listSubmittedOffers
      summary: List submitted operator offers for the authenticated brokerage
      description: The organization is derived from the API key. Supplier identity is not exposed. Requires offers:read.
      parameters:
        - in: query
          name: page
          schema:
            type: integer
            minimum: 1
            maximum: 10000
            default: 1
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 24
      responses:
        "200":
          description: Submitted offers, newest first. Response is private and not cacheable.
          headers:
            Cache-Control:
              schema: { type: string, example: "private, no-store" }
          content:
            application/json:
              schema:
                type: object
                required: [organization_id, page, limit, has_more, offers]
                properties:
                  organization_id: { type: string, format: uuid }
                  page: { type: integer }
                  limit: { type: integer }
                  has_more: { type: boolean }
                  offers:
                    type: array
                    items: { $ref: "#/components/schemas/Offer" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "429":
          description: The key exceeded 60 reads in the rolling minute.
          headers:
            Retry-After:
              schema: { type: string, example: "60" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorBody" }
        "503": { $ref: "#/components/responses/Error" }
  /empty-legs:
    get:
      operationId: listEmptyLegs
      summary: Pull the shared empty-leg collection once per UTC day
      description: >
        Requires empty_legs:read. Returns the public Limitless Sky RSS merged with stored empty-leg snapshot rows.
        Does not call the Aviapages directory. One successful pull per organization per UTC day.
        Failed authentication is not counted. A second pull the same day returns 429 with retry_at at the next UTC midnight.
      responses:
        "200":
          description: The current empty-leg collection. Response is private and not cacheable.
          headers:
            Cache-Control:
              schema: { type: string, example: "private, no-store" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EmptyLegCollection" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "429":
          description: This account already pulled empty legs on the current UTC day.
          headers:
            Retry-After:
              schema: { type: string, description: Seconds until the next UTC midnight., example: "3600" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EmptyLegRateLimit" }
        "503": { $ref: "#/components/responses/Error" }
components:
  securitySchemes:
    brokerBearer:
      type: http
      scheme: bearer
      bearerFormat: Jet Intelligence broker API key
  responses:
    Error:
      description: Request error
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ErrorBody" }
  schemas:
    ErrorBody:
      type: object
      required: [error]
      properties:
        error: { type: string }
    EmptyLegRateLimit:
      type: object
      required: [error, retry_at]
      properties:
        error: { type: string }
        retry_at: { type: string, format: date-time, description: Next UTC midnight, when another pull is allowed. }
    EmptyLeg:
      type: object
      required: [id, source, origin_code, destination_code, departure_date, title]
      properties:
        id: { type: string }
        source: { type: string, enum: [limitless_sky_rss, stored_snapshot] }
        origin_code: { type: string }
        destination_code: { type: string }
        origin_city: { type: [string, "null"] }
        destination_city: { type: [string, "null"] }
        origin_latitude: { type: [number, "null"] }
        origin_longitude: { type: [number, "null"] }
        destination_latitude: { type: [number, "null"] }
        destination_longitude: { type: [number, "null"] }
        departure_date: { type: string, format: date }
        aircraft: { type: [string, "null"] }
        operator_name: { type: [string, "null"] }
        title: { type: string }
        link: { type: [string, "null"] }
    EmptyLegCollection:
      type: object
      required: [organization_id, next_pull_at, sources, feed_fetched_at, feed_failed, count, empty_legs]
      properties:
        organization_id: { type: string, format: uuid }
        pulled_at: { type: [string, "null"], format: date-time }
        next_pull_at: { type: string, format: date-time }
        sources:
          type: array
          items: { type: string }
        feed_fetched_at: { type: string, format: date-time }
        feed_failed: { type: boolean }
        count: { type: integer }
        empty_legs:
          type: array
          items: { $ref: "#/components/schemas/EmptyLeg" }
    Offer:
      type: object
      required: [id, revision, aircraft_type, currency, price_state, availability, unknown_items, amenities]
      properties:
        id: { type: string, format: uuid }
        revision: { type: integer }
        aircraft_type: { type: [string, "null"] }
        currency: { type: string, minLength: 3, maxLength: 3 }
        total_amount: { type: [number, "null"] }
        price_state: { type: string }
        availability: { type: string }
        submitted_at: { type: [string, "null"], format: date-time }
        valid_until: { type: [string, "null"], format: date-time }
        unknown_items: { type: array, items: { type: string } }
        image_url: { type: [string, "null"], format: uri }
        amenities: { type: array, items: { type: string } }
        alternative_origin_icao: { type: [string, "null"] }
        alternative_destination_icao: { type: [string, "null"] }
        alternative_departure_local: { type: [string, "null"], description: Local datetime supplied by the operator. }
