openapi: 3.1.0
info:
  title: Armada Automated Ordering API
  version: 2.0.0
  summary: Merchant-authenticated v2 API for creating and tracking Armada deliveries.
  description: |
    The v2 API exposes the Armada delivery platform to merchant partners. Every
    request is authenticated with a merchant-scoped API key + secret using
    HMAC-SHA256 request signing. All endpoints are idempotent to retry when the
    response is a network error; duplicate keys de-dupe on `reference`.

    ## Authentication

    Send every request with three headers:

    - `Authorization: Key <api_key_value>`
    - `x-armada-timestamp: <milliseconds since epoch>`
    - `x-armada-signature: <hex HMAC-SHA256>`

    The signature is `HMAC_SHA256(secret, "${timestamp}.${method}.${path}.${body}")`.
    For `GET` requests the body is the empty string. The signature must arrive
    within 30 seconds of the timestamp.
  contact:
    name: Armada API support
    email: support@armadadelivery.com
    url: https://public-api-docs.armadadelivery.com
  license:
    name: Proprietary
    identifier: LicenseRef-Armada-Proprietary
servers:
  - url: https://api.armadadelivery.com
    description: Production. Default base URL for all examples. Use a Test-mode API key to simulate the full delivery lifecycle without dispatching real drivers.
  - url: https://sandbox.api.armadadelivery.com
    description: Sandbox. Fully isolated deployment with its own merchants and data. Optional — only use this if you need zero overlap with your production account.

security:
  - ApiKeyAuth: []

tags:
  - name: Deliveries
    description: Create, track, cancel, retry, and estimate deliveries.
  - name: Branches
    description: Manage the merchant's pickup locations.
  - name: Wallet
    description: Inspect the prepaid wallet balance.
  - name: Invoices
    description: Consult generated invoices.

paths:
  /v2/deliveries:
    post:
      tags: [Deliveries]
      summary: Create a delivery
      operationId: createDelivery
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/DeliveryRequest' }
      responses:
        '200':
          description: Delivery created.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Delivery' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /v2/deliveries/{id}:
    parameters:
      - { $ref: '#/components/parameters/DeliveryId' }
    get:
      tags: [Deliveries]
      summary: Retrieve a delivery
      operationId: getDelivery
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Delivery' } } }
        '404': { $ref: '#/components/responses/NotFound' }

  /v2/deliveries/{id}/cancel:
    parameters:
      - { $ref: '#/components/parameters/DeliveryId' }
    post:
      tags: [Deliveries]
      summary: Cancel a delivery
      operationId: cancelDelivery
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string, description: 'Optional free-text cancellation reason.' }
      responses:
        '200':
          description: Canceled
          content: { application/json: { schema: { $ref: '#/components/schemas/Delivery' } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '409':
          description: Delivery is not in a cancellable state.
          content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }

  /v2/deliveries/{id}/retry:
    parameters:
      - { $ref: '#/components/parameters/DeliveryId' }
    post:
      tags: [Deliveries]
      summary: Retry a failed delivery
      description: |
        Retries a delivery that reached the `failed` terminal state. Resets the
        dispatcher trial counter and re-enqueues the order.
      operationId: retryDelivery
      responses:
        '200':
          description: Retry queued.
          content: { application/json: { schema: { $ref: '#/components/schemas/Delivery' } } }
        '404': { $ref: '#/components/responses/NotFound' }
        '409':
          description: Delivery is not in a retryable state (only `failed` is retryable).
          content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }

  /v2/deliveries/estimate:
    post:
      tags: [Deliveries]
      summary: Estimate a delivery fee
      operationId: estimateDelivery
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EstimateRequest' }
      responses:
        '200':
          description: Estimate returned.
          content: { application/json: { schema: { $ref: '#/components/schemas/Estimate' } } }
        '400': { $ref: '#/components/responses/BadRequest' }

  /v2/deliveries/estimate/static:
    post:
      tags: [Deliveries]
      summary: Estimate a delivery fee using static pricing (no live traffic).
      operationId: estimateDeliveryStatic
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EstimateRequest' }
      responses:
        '200':
          description: Estimate returned.
          content: { application/json: { schema: { $ref: '#/components/schemas/Estimate' } } }

  /v2/branches:
    get:
      tags: [Branches]
      summary: List branches
      operationId: listBranches
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  branches:
                    type: array
                    items: { $ref: '#/components/schemas/Branch' }
    post:
      tags: [Branches]
      summary: Create a branch
      operationId: createBranch
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BranchInput' }
      responses:
        '201':
          description: Branch created.
          content: { application/json: { schema: { $ref: '#/components/schemas/Branch' } } }
        '400': { $ref: '#/components/responses/BadRequest' }

  /v2/branches/{id}:
    parameters:
      - { $ref: '#/components/parameters/BranchId' }
    get:
      tags: [Branches]
      summary: Get a branch
      operationId: getBranch
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Branch' } } } }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [Branches]
      summary: Update a branch
      operationId: updateBranch
      requestBody:
        required: true
        content: { application/json: { schema: { $ref: '#/components/schemas/BranchInput' } } }
      responses:
        '200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Branch' } } } }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Branches]
      summary: Delete a branch
      operationId: deleteBranch
      responses:
        '204': { description: Deleted }
        '404': { $ref: '#/components/responses/NotFound' }

  /v2/wallet:
    get:
      tags: [Wallet]
      summary: Get wallet balance
      operationId: getWallet
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Wallet' } } }

  /v2/invoices:
    get:
      tags: [Invoices]
      summary: List invoices
      operationId: listInvoices
      parameters:
        - { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
        - { name: perPage, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } }
        - { name: status, in: query, schema: { type: string, enum: [unpaid, paid, topup, all], default: all } }
        - { name: periodBegin, in: query, schema: { type: string, format: date } }
        - { name: periodEnd, in: query, schema: { type: string, format: date } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  page: { type: integer }
                  perPage: { type: integer }
                  total: { type: integer }
                  invoices:
                    type: array
                    items: { $ref: '#/components/schemas/Invoice' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /v2/invoices/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string, pattern: '^[a-f0-9]{24}$' }
    get:
      tags: [Invoices]
      summary: Get an invoice
      operationId: getInvoice
      responses:
        '200':
          description: OK
          content: { application/json: { schema: { $ref: '#/components/schemas/Invoice' } } }
        '404': { $ref: '#/components/responses/NotFound' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: |
        `Authorization: Key <api_key_value>` combined with HMAC-SHA256 signing
        (`x-armada-timestamp` + `x-armada-signature` headers). See the Authentication
        section of the API overview for the exact formula.

  parameters:
    DeliveryId:
      name: id
      in: path
      required: true
      schema: { type: string, pattern: '^[a-f0-9]{24}$' }
    BranchId:
      name: id
      in: path
      required: true
      schema: { type: string, pattern: '^[a-f0-9]{24}$' }

  responses:
    BadRequest:
      description: Validation error.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Unauthorized:
      description: Missing or invalid credentials / signature.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Forbidden:
      description: API key lacks the required permission.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    NotFound:
      description: Resource not found or not owned by this merchant.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string, description: Machine-readable error code. }
        message: { type: string, description: Human-readable description. }

    Payment:
      type: object
      required: [amount, type]
      properties:
        amount: { type: number, exclusiveMinimum: 0 }
        type: { type: string, enum: [paid, cash] }

    LocationAddress:
      type: object
      required: [contact_name, contact_phone, latitude, longitude]
      properties:
        contact_name: { type: string }
        contact_phone: { type: string }
        latitude: { type: number }
        longitude: { type: number }
        first_line: { type: string }
        floor: { type: string }
        apartment: { type: string }
        instructions: { type: string }

    KuwaitAddress:
      type: object
      required: [contact_name, contact_phone, area, block, street, building]
      properties:
        contact_name: { type: string }
        contact_phone: { type: string }
        area: { type: string }
        block: { type: string }
        street: { type: string }
        building: { type: string }
        floor: { type: string }
        apartment: { type: string }
        instructions: { type: string }

    BahrainAddress: { $ref: '#/components/schemas/KuwaitAddress' }

    KsaAddress:
      type: object
      required: [contact_name, contact_phone, city, street, district, building]
      properties:
        contact_name: { type: string }
        contact_phone: { type: string }
        city: { type: string }
        street: { type: string }
        district: { type: string }
        building: { type: string }
        floor: { type: string }
        apartment: { type: string }
        instructions: { type: string }

    KsaShortAddress:
      type: object
      required: [contact_name, contact_phone, short_address]
      properties:
        contact_name: { type: string }
        contact_phone: { type: string }
        short_address: { type: string }
        instructions: { type: string }

    BranchOrigin:
      type: object
      required: [branch_id]
      properties:
        branch_id: { type: string, pattern: '^[a-f0-9]{24}$' }

    DeliveryRequest:
      type: object
      required: [reference, payment, origin_format, destination_format, origin, destination]
      properties:
        reference: { type: string, description: Partner-supplied unique id used for de-duplication. }
        scheduled_date: { type: string, format: date-time }
        payment: { $ref: '#/components/schemas/Payment' }
        origin_format: { type: string, enum: [location_format, branch_format] }
        origin:
          oneOf:
            - $ref: '#/components/schemas/BranchOrigin'
            - $ref: '#/components/schemas/LocationAddress'
        destination_format: { type: string, enum: [location_format, kuwait_format, bahrain_format, ksa_format, ksa_short_format] }
        destination:
          oneOf:
            - $ref: '#/components/schemas/LocationAddress'
            - $ref: '#/components/schemas/KuwaitAddress'
            - $ref: '#/components/schemas/BahrainAddress'
            - $ref: '#/components/schemas/KsaAddress'
            - $ref: '#/components/schemas/KsaShortAddress'

    EstimateRequest:
      type: object
      required: [origin_format, destination_format, origin, destination]
      properties:
        origin_format: { type: string, enum: [location_format, branch_format] }
        origin:
          oneOf:
            - $ref: '#/components/schemas/BranchOrigin'
            - $ref: '#/components/schemas/LocationAddress'
        destination_format: { type: string, enum: [location_format, kuwait_format, bahrain_format, ksa_format, ksa_short_format] }
        destination:
          oneOf:
            - $ref: '#/components/schemas/LocationAddress'
            - $ref: '#/components/schemas/KuwaitAddress'
            - $ref: '#/components/schemas/BahrainAddress'
            - $ref: '#/components/schemas/KsaAddress'
            - $ref: '#/components/schemas/KsaShortAddress'

    Estimate:
      type: object
      properties:
        fee: { type: number }
        currency: { type: string }
        distance: { type: number, description: Meters. }
        duration: { type: number, description: Seconds. }

    Delivery:
      type: object
      properties:
        id: { type: string }
        reference: { type: string }
        status:
          type: string
          enum: [pending, accepted, dispatched, en_route, completed, canceled, failed]
        code: { type: string }
        branch: { type: string }
        amount: { type: number }
        currency: { type: string }
        deliveryFee: { type: number }
        distance: { type: number }
        duration: { type: number }
        customerName: { type: string }
        customerPhone: { type: string }
        driverId: { type: ['string', 'null'] }
        driverName: { type: ['string', 'null'] }
        driverPhone: { type: ['string', 'null'] }
        driverLocation:
          type: object
          properties:
            latitude: { type: ['number', 'null'] }
            longitude: { type: ['number', 'null'] }
        trackingLink: { type: string, format: uri }
        testMode: { type: boolean }
        createdAt: { type: string, format: date-time }

    BranchInput:
      type: object
      required: [name, phone, address]
      properties:
        name: { type: string }
        phone: { type: string }
        address: { type: string }
        location:
          type: object
          properties:
            lat: { type: number }
            lng: { type: number }

    Branch:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        phone: { type: string }
        address: { type: string }
        latitude: { type: number }
        longitude: { type: number }
        created_at: { type: string, format: date-time }

    Wallet:
      type: object
      properties:
        balance: { type: number }
        currency: { type: string }
        warningBalanceLevel: { type: ['number', 'null'] }

    Invoice:
      type: object
      properties:
        id: { type: string }
        invoiceNo: { type: string }
        type:
          type: string
          enum: [REGULAR, CUSTOMER_ORDER, WALLET, TOPUP_WALLET, REFUND_WALLET, REFUND, TAP_WALLET]
        status:
          type: string
          enum: [ISSUED, PAID, PENDING, SENT, CANCELED, AUTHORIZED]
        amount: { type: number }
        currency: { type: string }
        ordersCount: { type: integer }
        deliveryTripsCount: { type: integer }
        periodBegin: { type: string, format: date }
        periodEnd: { type: string, format: date }
        dueOn: { type: string, format: date }
        createdAt: { type: string, format: date-time }
