openapi: 3.1.1
info:
  title: Boring Order Tracker Operator Integration API
  version: 1.0.1
  summary: Consent-bound, pull-first data access for approved BOT integrations.
  description: |
    Every operation requires an active environment-specific BOT client certificate.
    IDs are random and integration-specific. VINs, real order numbers, BOT account
    identifiers, Tesla identifiers, credentials, raw provider responses, and
    individual statistics records are never returned. Approved order and stock-
    vehicle snapshots include option_codes and the other non-personal fields in
    the referenced schemas; their history endpoints retain every permitted
    change, including indexed option-code changes.
  contact:
    url: https://integration.boringordertracker.com
servers:
  - url: https://api.integration.boringordertracker.com/v1
    description: Production and synthetic sandbox; the certificate selects the environment.
security:
  - mutualTls: []
tags:
  - name: Client
  - name: Linking
  - name: Synchronization
  - name: Orders
  - name: Vehicles
  - name: Statistics
  - name: Revocations
paths:
  /capabilities:
    get:
      tags: [Client]
      operationId: getCapabilities
      summary: Inspect the authenticated environment, scopes, and quotas.
      responses:
        '200':
          description: Effective client capabilities.
          headers:
            RateLimit-Limit: {$ref: '#/components/headers/RateLimitLimit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimitRemaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimitReset'}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Capabilities'}
        default: {$ref: '#/components/responses/Problem'}
  /link-codes/redeem:
    post:
      tags: [Linking]
      operationId: redeemLinkCode
      summary: Redeem one integration-bound ten-digit one-time code.
      description: >-
        Codes retain leading zeroes, expire after ten minutes, and are single-use.
        Redemption succeeds only after the connection initial sync and every active
        resource snapshot/history are complete. A recoverably incomplete connection
        receives the same opaque invalid-code response and the code is not consumed.
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '../schemas/link-code-redeem-request.json'}
      responses:
        '200':
          description: Exact connection and granted integration-specific resources.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/RedeemedConnection'}
        '400': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
        '429': {$ref: '#/components/responses/RateLimited'}
        default: {$ref: '#/components/responses/Problem'}
  /sync:
    get:
      tags: [Synchronization]
      operationId: syncAuthorizedResources
      summary: Recover or incrementally synchronize authorized resources and tombstones.
      parameters:
        - name: updated_since
          in: query
          description: Omit for initial sync; otherwise send the opaque next_cursor unchanged.
          schema: {type: string, minLength: 20, maxLength: 1024}
        - {$ref: '#/components/parameters/Limit'}
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Ordered upserts and revocation tombstones.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/sync-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        default: {$ref: '#/components/responses/Problem'}
  /orders/{id}:
    get:
      tags: [Orders]
      operationId: getOrder
      summary: Read the current permitted order snapshot.
      description: Returns only the grant-bound allowlist, including option_codes when received; never a VIN or true order number.
      parameters:
        - {$ref: '#/components/parameters/ResourceId'}
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Current allowlisted snapshot.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/order-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        '404': {$ref: '#/components/responses/Problem'}
        '410': {$ref: '#/components/responses/Gone'}
        default: {$ref: '#/components/responses/Problem'}
  /orders/{id}/history:
    get:
      tags: [Orders]
      operationId: getOrderHistory
      summary: Read the complete permitted, append-only order history.
      description: Includes indexed option_codes changes and all other allowlisted order-field changes for the active grant.
      parameters:
        - {$ref: '#/components/parameters/ResourceId'}
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Ordered history page.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/history-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        '410': {$ref: '#/components/responses/Gone'}
        default: {$ref: '#/components/responses/Problem'}
  /vehicles/{id}:
    get:
      tags: [Vehicles]
      operationId: getInventoryVehicle
      summary: Read the current permitted inventory-vehicle snapshot.
      description: Returns the selected stock vehicle's allowlisted configuration, option_codes, operating, charging, software and tyre fields.
      parameters:
        - {$ref: '#/components/parameters/ResourceId'}
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Current allowlisted snapshot.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/vehicle-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        '410': {$ref: '#/components/responses/Gone'}
        default: {$ref: '#/components/responses/Problem'}
  /vehicles/{id}/history:
    get:
      tags: [Vehicles]
      operationId: getInventoryVehicleHistory
      summary: Read every received, deduplicated permitted vehicle change.
      description: Includes indexed option_codes changes and every other received allowlisted stock-vehicle change.
      parameters:
        - {$ref: '#/components/parameters/ResourceId'}
        - {$ref: '#/components/parameters/Cursor'}
        - {$ref: '#/components/parameters/Limit'}
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Ordered history page.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/history-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        '410': {$ref: '#/components/responses/Gone'}
        default: {$ref: '#/components/responses/Problem'}
  /statistics/orders:
    get:
      tags: [Statistics]
      operationId: getOrderStatistics
      summary: Read a fixed, privacy-thresholded aggregate generation.
      description: No individual record is returned. Every published cell has at least five contributors.
      parameters:
        - {$ref: '#/components/parameters/IfNoneMatch'}
      responses:
        '200':
          description: Latest safe published generation.
          headers:
            ETag: {$ref: '#/components/headers/ETag'}
          content:
            application/json:
              schema: {$ref: '../schemas/statistics-response.json'}
        '304': {$ref: '#/components/responses/NotModified'}
        '404': {$ref: '#/components/responses/Problem'}
        default: {$ref: '#/components/responses/Problem'}
  /revocations/{id}/ack:
    post:
      tags: [Revocations]
      operationId: acknowledgeRevocationDeletion
      summary: Confirm deletion of every retained operator copy.
      parameters:
        - name: id
          in: path
          required: true
          schema: {$ref: '#/components/schemas/OpaqueId'}
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, pattern: '^[A-Za-z0-9._~-]{8,128}$'}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '../schemas/revocation-ack-request.json'}
      responses:
        '200':
          description: Idempotent server-side acknowledgement receipt.
          content:
            application/json:
              schema: {$ref: '../schemas/revocation-ack-response.json'}
        '404': {$ref: '#/components/responses/Problem'}
        '422': {$ref: '#/components/responses/Problem'}
        default: {$ref: '#/components/responses/Problem'}
components:
  securitySchemes:
    mutualTls:
      type: mutualTLS
      description: BOT-issued environment-specific X.509 client certificate.
  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      description: Random ID scoped to this integration. It is not a BOT or provider identifier.
      schema: {$ref: '#/components/schemas/OpaqueId'}
    Cursor:
      name: cursor
      in: query
      schema: {type: string, minLength: 20, maxLength: 1024}
    Limit:
      name: limit
      in: query
      schema: {type: integer, minimum: 1, maximum: 500, default: 500}
    IfNoneMatch:
      name: If-None-Match
      in: header
      schema: {type: string}
  headers:
    ETag:
      description: Strong entity tag for this authorized representation.
      schema: {type: string}
    RateLimitLimit:
      schema: {type: integer, minimum: 1}
    RateLimitRemaining:
      schema: {type: integer, minimum: 0}
    RateLimitReset:
      description: UTC Unix timestamp when the limiting bucket resets.
      schema: {type: integer, minimum: 0}
  responses:
    Problem:
      description: RFC 9457 problem detail.
      content:
        application/problem+json:
          schema: {$ref: '../schemas/problem.json'}
    RateLimited:
      description: Quota exhausted.
      headers:
        Retry-After: {schema: {type: integer, minimum: 1}}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimitLimit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimitRemaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimitReset'}
      content:
        application/problem+json:
          schema: {$ref: '../schemas/problem.json'}
    NotModified:
      description: The authorized representation matches If-None-Match; no body is returned.
      headers:
        ETag: {$ref: '#/components/headers/ETag'}
    Gone:
      description: Consent was revoked; delete retained copies and acknowledge the tombstone.
      content:
        application/problem+json:
          schema: {$ref: '../schemas/problem.json'}
  schemas:
    OpaqueId:
      type: string
      pattern: '^[A-Za-z0-9_-]{8,80}$'
    Capabilities:
      type: object
      additionalProperties: false
      required: [api_version, environment, integration_id, integration_status, production_data_enabled, scopes, quotas, polling, server_time]
      properties:
        api_version: {const: v1}
        environment: {enum: [sandbox, production]}
        integration_id: {$ref: '#/components/schemas/OpaqueId'}
        integration_status: {enum: [draft, sandbox, submitted, approved, active, rejected, suspended]}
        production_data_enabled:
          type: boolean
          description: False for a production certificate until BOT has completed every production-release gate. Sandbox remains synthetic and available.
        scopes: {type: array, uniqueItems: true, items: {type: string}}
        quotas: {type: object, additionalProperties: {type: integer}}
        polling:
          type: object
          additionalProperties: false
          required: [recommended_interval_seconds]
          properties:
            recommended_interval_seconds: {type: integer, const: 900}
        server_time: {type: string, format: date-time}
    RedeemedConnection:
      type: object
      additionalProperties: false
      required: [connection_id, state, history_complete, resources]
      properties:
        connection_id: {$ref: '#/components/schemas/OpaqueId'}
        state: {const: active}
        history_complete: {const: true}
        resources:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [type, id]
            properties:
              type: {enum: [order, vehicle]}
              id: {$ref: '#/components/schemas/OpaqueId'}
