openapi: 3.0.3
info:
  title: Palm Open API
  version: "1.0"
  description: |
    Public API for third-party integrations with Palm POS.

servers:
  - url: https://lite-dev.palmnet.co
    description: Production

security:
  - ApiKeyHeader: []

paths:
  /v1/orgs/{orgId}/orders:
    get:
      operationId: listOrgOrders
      summary: List orders
      description: |
        Retrieve orders across all stores in the organization,
        sorted by creation time (newest first). Supports filtering
        and cursor-based pagination.
      tags:
        - Orders
      security:
        - ApiKeyHeader: []
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Organization ID

        - name: store_id
          in: query
          schema:
            type: string
            format: uuid
          description: Filter by a specific store

        - name: view
          in: query
          schema:
            type: string
            enum: [all, active, history]
            default: all
          description: >
            `all` — no filter;
            `active` — excludes completed/cancelled;
            `history` — only completed/cancelled

        - name: payment_status
          in: query
          schema:
            type: string
          description: Comma-separated payment statuses (e.g. `paid,unpaid`)

        - name: fulfillment_status
          in: query
          schema:
            type: string
          description: Comma-separated fulfillment statuses (e.g. `completed,cancelled`)

        - name: source
          in: query
          schema:
            type: string
          description: Comma-separated order sources (e.g. `pos,kiosk`)

        - name: from
          in: query
          schema:
            type: string
          description: Start date/time (RFC 3339 or `YYYY-MM-DD`)

        - name: to
          in: query
          schema:
            type: string
          description: End date/time (RFC 3339 or `YYYY-MM-DD`; date-only = end of day)

        - name: amount_min
          in: query
          schema:
            type: integer
          description: Minimum totalCents (inclusive)

        - name: amount_max
          in: query
          schema:
            type: integer
          description: Maximum totalCents (inclusive)

        - name: page_size
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 200
          description: >
            Number of orders per page (1–200).
            Capped to 50 when `expand` includes `items`.

        - name: before
          in: query
          schema:
            type: string
            format: date-time
          description: >
            Pagination cursor — pass the `createdAt` of the last order
            from the previous page (RFC 3339 Nano)

        - name: expand
          in: query
          schema:
            type: string
          description: >
            Comma-separated list of sub-resources to inline in each order object.
            Allowed values: `items`, `payments`, `refunds`, `tax_breakdown`.
            When specified, the server batch-loads the requested sub-resources
            for all orders in the current page and includes them in the response.
          example: "items,payments"

      responses:
        "200":
          description: Orders retrieved successfully
          content:
            application/json:
              schema:
                type: object
                required: [orders, hasMore]
                properties:
                  orders:
                    type: array
                    items:
                      $ref: "#/components/schemas/Order"
                  hasMore:
                    type: boolean
                    description: Whether more orders exist beyond this page
              example:
                orders:
                  - id: "550e8400-e29b-41d4-a716-446655440000"
                    storeId: "660e8400-e29b-41d4-a716-446655440001"
                    orderVersion: 3
                    serviceType: dine_in
                    paymentStatus: paid
                    fulfillmentStatus: completed
                    subtotalCents: 2500
                    discountCents: 0
                    manualDiscountCents: 0
                    taxCents: 250
                    totalCents: 2750
                    note: ""
                    source: pos
                    tzOffsetMinutes: 60
                    createdAt: "2024-06-15T14:30:00Z"
                hasMore: false
        "400":
          description: Invalid request parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient scope or org mismatch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /v1/orgs/{orgId}/orders/{orderId}:
    get:
      operationId: getOrgOrder
      summary: Get order details
      description: |
        Retrieve full details of a single order including line items,
        refunds, and tax breakdown.
      tags:
        - Orders
      security:
        - ApiKeyHeader: []
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Organization ID

        - name: orderId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Order ID

      responses:
        "200":
          description: Order retrieved successfully
          content:
            application/json:
              schema:
                type: object
                required: [order]
                properties:
                  order:
                    $ref: "#/components/schemas/OrderDetail"
        "401":
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "403":
          description: Insufficient scope or org mismatch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Order not found or does not belong to the organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key passed in the `X-Api-Key` header

  schemas:
    Order:
      type: object
      required:
        - id
        - storeId
        - orderVersion
        - serviceType
        - paymentStatus
        - fulfillmentStatus
        - subtotalCents
        - discountCents
        - manualDiscountCents
        - taxCents
        - totalCents
        - note
        - source
        - tzOffsetMinutes
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        storeId:
          type: string
          format: uuid
        orderVersion:
          type: integer
        serviceType:
          type: string
          description: "`dine_in`, `takeaway`, `delivery`, etc."
        paymentStatus:
          type: string
          enum: [unpaid, partial, paid, refunded]
        fulfillmentStatus:
          type: string
          enum: [pending, pending_acceptance, accepted, preparing, ready, completed, cancelled]
        subtotalCents:
          type: integer
          description: Subtotal before discounts and tax (cents)
        discountCents:
          type: integer
          description: Promotion/coupon discount (cents)
        manualDiscountCents:
          type: integer
          description: Manual discount by staff (cents)
        taxCents:
          type: integer
          description: Tax amount (cents)
        totalCents:
          type: integer
          description: Final total (cents)
        couponCode:
          type: string
          nullable: true
        sequenceNumber:
          type: integer
          nullable: true
          description: Daily sequence number
        ticketNumber:
          type: string
          nullable: true
        note:
          type: string
        source:
          type: string
          description: "Order origin: `pos`, `kiosk`, `online`, etc."
        tzOffsetMinutes:
          type: integer
          description: Store timezone offset from UTC in minutes
        paidAt:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when payment was completed (null if unpaid)
        createdAt:
          type: string
          format: date-time
        tableId:
          type: string
          format: uuid
          nullable: true
        tableName:
          type: string
          nullable: true
        memberId:
          type: string
          format: uuid
          nullable: true
        memberName:
          type: string
          nullable: true
        memberPhone:
          type: string
          nullable: true
        shippingAddressId:
          type: string
          format: uuid
          nullable: true
          description: Shipping address ID (for delivery orders)
        parentOrderId:
          type: string
          format: uuid
          nullable: true
          description: Parent order ID (for split orders)
        pickupName:
          type: string
          nullable: true
        pickupPhone:
          type: string
          nullable: true
        pickupAt:
          type: string
          format: date-time
          nullable: true
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrderItem"
          description: >
            Line items. Present in detail endpoint; in list endpoint only
            when `expand=items` is requested.
        payments:
          type: array
          items:
            $ref: "#/components/schemas/OrderPayment"
          description: >
            Payment records. Present in detail endpoint; in list endpoint
            only when `expand=payments` is requested.
        refunds:
          type: array
          items:
            $ref: "#/components/schemas/OrderRefund"
          description: >
            Refund records. Present in detail endpoint; in list endpoint
            only when `expand=refunds` is requested.
        taxBreakdown:
          type: array
          items:
            $ref: "#/components/schemas/TaxBreakdownEntry"
          description: >
            Per-rate tax breakdown. Present in detail endpoint; in list
            endpoint only when `expand=tax_breakdown` is requested.

    OrderPayment:
      type: object
      required:
        - id
        - orderId
        - paymentMethodId
        - amountCents
        - note
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        orderId:
          type: string
          format: uuid
        paymentMethodId:
          type: string
          format: uuid
        paymentMethodName:
          type: object
          additionalProperties:
            type: string
          description: "Localized name, e.g. `{\"_base\": \"Cash\", \"en\": \"Cash\"}`"
        paymentMethodCode:
          type: string
          description: "Machine-readable code, e.g. `cash`, `card`"
        amountCents:
          type: integer
          description: Amount paid (cents)
        tenderCents:
          type: integer
          nullable: true
          description: Amount tendered if different (cash overpayment)
        note:
          type: string
        createdAt:
          type: string
          format: date-time

    OrderDetail:
      description: >
        Full order object returned by the detail endpoint.
        Always includes items, payments, refunds, taxBreakdown,
        and appliedPromotions.
      allOf:
        - $ref: "#/components/schemas/Order"
        - type: object
          properties:
            appliedPromotions:
              type: array
              nullable: true
              items:
                $ref: "#/components/schemas/AppliedPromotion"

    OrderItem:
      type: object
      required:
        - id
        - orderId
        - productId
        - qty
        - unitPriceCents
        - amountCents
        - discountCents
        - manualDiscountCents
        - taxCents
        - refundedAmountCents
        - refundedQty
        - itemRole
      properties:
        id:
          type: string
          format: uuid
        orderId:
          type: string
          format: uuid
        productId:
          type: string
          format: uuid
        productName:
          type: object
          additionalProperties:
            type: string
          description: "Localized product name, e.g. `{\"_base\": \"Latte\", \"en\": \"Latte\"}`"
        variantId:
          type: string
          format: uuid
          nullable: true
        variantName:
          type: object
          additionalProperties:
            type: string
          nullable: true
        qty:
          type: number
        unitPriceCents:
          type: integer
        amountCents:
          type: integer
          description: Line total before discount (cents)
        discountCents:
          type: integer
        manualDiscountCents:
          type: integer
        isPriceOverride:
          type: boolean
        modifiers:
          type: array
          items:
            $ref: "#/components/schemas/OrderItemModifier"
        taxRateId:
          type: string
          nullable: true
        taxCents:
          type: integer
        refundedAmountCents:
          type: integer
        refundedQty:
          type: number
        submitRound:
          type: integer
          description: Submit round number (increments on each kitchen send)
        deltaQty:
          type: number
          description: Quantity change in the current submit round
        itemRole:
          type: string
          enum: [normal, combo_parent, combo_child]
        parentItemId:
          type: string
          format: uuid
          nullable: true
        comboGroupId:
          type: string
          description: Combo group ID within the combo product
        comboItemId:
          type: string
          description: Combo item ID within the group
        comboExtraCents:
          type: integer
          description: Extra charge for this combo selection (cents)

    AppliedPromotion:
      type: object
      required:
        - promotionId
        - triggerType
        - name
        - discountCents
      properties:
        promotionId:
          type: string
          format: uuid
        triggerType:
          type: string
          enum: [automatic, coupon]
          description: How the promotion was triggered
        memberCouponId:
          type: string
          format: uuid
          nullable: true
          description: Member coupon instance ID (when triggered by coupon)
        name:
          type: object
          additionalProperties:
            type: string
          description: "Localized promotion name, e.g. `{\"_base\": \"10% Off\", \"en\": \"10% Off\"}`"
        discountCents:
          type: integer
          description: Total discount from this promotion (cents)
        lineAllocations:
          type: array
          items:
            $ref: "#/components/schemas/DiscountAllocation"

    DiscountAllocation:
      type: object
      required:
        - lineIndex
        - discountCents
        - promotionId
      properties:
        lineIndex:
          type: integer
          description: Index into the items array
        discountCents:
          type: integer
          description: Discount allocated to this item (cents)
        promotionId:
          type: string
          format: uuid

    OrderItemModifier:
      type: object
      required:
        - id
        - name
        - priceDeltaCents
      properties:
        id:
          type: string
          description: Modifier option ID
        name:
          type: object
          additionalProperties:
            type: string
          description: "Localized modifier name, e.g. `{\"_base\": \"Extra cheese\", \"en\": \"Extra cheese\"}`"
        priceDeltaCents:
          type: integer
          description: Price adjustment (cents, can be negative)

    OrderRefund:
      type: object
      required:
        - id
        - orderId
        - amountCents
        - reason
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        orderId:
          type: string
          format: uuid
        amountCents:
          type: integer
        reason:
          type: string
        createdAt:
          type: string
          format: date-time
        items:
          type: array
          items:
            $ref: "#/components/schemas/OrderRefundItem"
        payments:
          type: array
          items:
            $ref: "#/components/schemas/OrderRefundPayment"

    OrderRefundItem:
      type: object
      required:
        - id
        - refundId
        - orderItemId
        - qty
        - amountCents
        - taxCents
      properties:
        id:
          type: string
          format: uuid
        refundId:
          type: string
          format: uuid
        orderItemId:
          type: string
          format: uuid
        qty:
          type: number
        amountCents:
          type: integer
        taxCents:
          type: integer

    OrderRefundPayment:
      type: object
      required:
        - id
        - refundId
        - paymentMethodId
        - amountCents
      properties:
        id:
          type: string
          format: uuid
        refundId:
          type: string
          format: uuid
        paymentMethodId:
          type: string
          format: uuid
        paymentMethodName:
          type: object
          additionalProperties:
            type: string
        paymentMethodCode:
          type: string
        amountCents:
          type: integer

    TaxBreakdownEntry:
      type: object
      required:
        - taxRateId
        - taxRatePercent
        - taxCents
      properties:
        taxRateId:
          type: string
          description: Tax rate ID (empty string if unset)
        taxRatePercent:
          type: number
        taxCents:
          type: integer

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Human-readable error message
        request_id:
          type: string
          description: Unique request ID for debugging
