> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endl.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an order

> Places a fulfilment instruction. Returns **`202 Accepted`** with an order id.

The order is **not complete** when this returns, and the response never contains vouchers. A `201` would imply it was finished.

There is no confirmation step and **no cancellation endpoint** — an accepted order either fulfils or produces a refund obligation. Validate before you send.

`amount_minor` must equal Σ(`denomination_minor` × `qty`) over the items. That is checked **asynchronously**: a mismatch is accepted here, then moves to `FAILED` with `state_reason: AMOUNT_MISMATCH` and raises a refund obligation. Compute the total yourself before sending.

## Idempotency

| You send                              | Endl answers                       | Effect                                               |
| ------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
| Same key, **identical body**          | `202` with the original `order_id` | Nothing new is created. Safe to repeat indefinitely. |
| Same key, **different body**          | `409 IDEMPOTENCY_KEY_REUSED`       | Nothing is created.                                  |
| New key, **same `partner_order_ref`** | `409 PARTNER_ORDER_REF_DUPLICATE`  | Nothing is created; the existing id is returned.     |

<Warning>
  The comparison is over the **raw bytes you sent**, so a retry must transmit
  byte-identical content — same field order, same whitespace. Re-serialising from
  an object between attempts can change those bytes and turn a safe retry into a
  `409`. Serialise once, then hash and send the same string.
</Warning>

Use a fresh key per distinct order; a UUID is the simplest scheme.


## OpenAPI

````yaml api-reference/endl-giftcard-api.json POST /orders
openapi: 3.1.0
info:
  title: Endl Giftcard API
  version: '1'
  description: >-
    Order gift cards, poll them to completion, and retrieve the redemption
    material.


    Five endpoints, one signing scheme, and one rule that governs everything
    else: **an order is a fulfilment instruction, and it is accepted before it
    is finished.**


    This is a separate service from the Endl Partner API. It does not use
    `API-KEY`, it is not under `/api/v0`, and it does not take an `Api-Version`
    header. Requests are signed with HMAC-SHA256.
servers:
  - url: https://api-sandbox.endl.io/giftcards
    description: Sandbox — confirm the host with Endl
security: []
paths:
  /orders:
    post:
      tags:
        - Orders
      summary: Create an order
      description: >-
        Places a fulfilment instruction. Returns **`202 Accepted`** with an
        order id.


        The order is **not complete** when this returns, and the response never
        contains vouchers. A `201` would imply it was finished.


        There is no confirmation step and **no cancellation endpoint** — an
        accepted order either fulfils or produces a refund obligation. Validate
        before you send.


        `amount_minor` must equal Σ(`denomination_minor` × `qty`) over the
        items. That is checked **asynchronously**: a mismatch is accepted here,
        then moves to `FAILED` with `state_reason: AMOUNT_MISMATCH` and raises a
        refund obligation. Compute the total yourself before sending.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/Timestamp'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - partner_order_ref
                - amount_minor
                - currency
                - payment_ref
                - items
              properties:
                partner_order_ref:
                  type: string
                  description: >-
                    Your own reference. Trimmed; 1–200 characters after
                    trimming. **Unique across your orders** — a repeat is a
                    `409`.
                amount_minor:
                  type: string
                  description: >-
                    Total in minor units. Must equal Σ(`denomination_minor` ×
                    `qty`). A number must be a safe integer; a string must match
                    `^[0-9]+$`.
                currency:
                  type: string
                  description: >-
                    `^[A-Z]{3}$`. Applies to the whole order — lower case is
                    rejected, and a mixed-currency order is refused.
                payment_ref:
                  type: string
                  description: >-
                    Your payment reference, 1–200 characters. Recorded but never
                    interpreted or verified.
                items:
                  type: array
                  minItems: 1
                  maxItems: 50
                  description: 1–50 entries. An empty array is a `VALIDATION_ERROR`.
                  items:
                    type: object
                    required:
                      - canonical_sku
                      - denomination_minor
                      - qty
                    properties:
                      canonical_sku:
                        type: string
                        description: From the catalogue. 1–200 characters.
                      denomination_minor:
                        type: string
                        description: Face value of **one card**, not the line total.
                      qty:
                        type: integer
                        description: >-
                          1–1000. Above 1000 is `QUANTITY_LIMIT_EXCEEDED`, not a
                          generic validation error.
                      currency:
                        type: string
                        description: >-
                          Optional. Defaults to the order currency; if sent it
                          must match exactly.
            example:
              partner_order_ref: ORD-10294
              amount_minor: '100000'
              currency: INR
              payment_ref: pi_3QXk2s
              items:
                - canonical_sku: BIGBASKET--IN--INR
                  denomination_minor: '50000'
                  qty: 2
      responses:
        '202':
          description: Accepted. The order will be worked on.
          content:
            application/json:
              example:
                order_id: 9f1c8a44-2b7e-4d31-9a6f-5c0e7b2d81a3
                state: PENDING
        '400':
          description: >-
            Malformed body, a missing field, a bad or mixed currency, an unsafe
            integer, or a missing `Idempotency-Key`.
          content:
            application/json:
              example:
                code: VALIDATION_ERROR
                message: items must be a non-empty array
        '401':
          description: Missing headers, an unknown key id, or an inactive key.
          content:
            application/json:
              example:
                code: AUTH_INVALID_KEY
                message: credential could not be verified
        '403':
          description: Authenticated, but the key lacks the scope this route needs.
          content:
            application/json:
              example:
                code: SCOPE_FORBIDDEN
                message: key lacks the required scope
        '409':
          description: Same `Idempotency-Key`, different body. Nothing is created.
          content:
            application/json:
              example:
                code: IDEMPOTENCY_KEY_REUSED
                message: idempotency key was used with a different body
        '413':
          description: Request body above 1 MiB.
          content:
            application/json:
              example:
                code: VALIDATION_ERROR
                message: request body too large
        '429':
          description: >-
            Rate limited. Honour the `Retry-After` header, in whole seconds,
            never below 1.
          content:
            application/json:
              example:
                code: RATE_LIMITED
                message: rate limit exceeded
        '500':
          description: Unexpected server error. Retry with exponential backoff.
          content:
            application/json:
              example:
                code: INTERNAL_ERROR
                message: unexpected error
      security:
        - apiKeyId: []
        - signature: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        Required on `POST /orders`. A stable string — a UUID is the simplest
        scheme — that makes the call safe to retry. Absent means
        `VALIDATION_ERROR`.
      schema:
        type: string
        minLength: 1
      example: 6f3c2a10-8f4e-4a1b-9c77-3e5d2b0a1f88
    Timestamp:
      name: X-Timestamp
      in: header
      required: false
      description: >-
        Unix seconds. Optional, carried for symmetry — send the same value as
        the `t=` inside `X-Signature`, which is the one actually validated.
      schema:
        type: string
      example: '1758470400'
  securitySchemes:
    apiKeyId:
      type: apiKey
      in: header
      name: X-Api-Key-Id
      description: >-
        Your key id, verbatim. Public — it identifies the credential. The secret
        is never transmitted.
    signature:
      type: apiKey
      in: header
      name: X-Signature
      description: >-
        `t=<unix-seconds>,v1=<hex64>` — HMAC-SHA256 over the canonical string.
        See [Signing requests](/giftcards/authentication).

````