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

# Create order

> **Call this after the diner completes payment and is redirected back.**

This endpoint does three things in sequence:
1. Validates the cart (items, timeslot, minimum order, etc.)
2. Checks payment status **in real-time** against the payment processor
   (does NOT depend on webhooks — makes a synchronous call to verify)
3. If fully paid, converts the cart to a confirmed order

Returns the full order object on success, or `422` with specific errors
if the cart is invalid or payment hasn't completed yet.

If the order was already created (e.g. duplicate call), returns the existing order.

After success, the session automatically gets a new empty cart.




## OpenAPI

````yaml post /cart/order
openapi: 3.1.0
info:
  title: Atlas Storefront API
  version: 1.0.0
  description: ''
servers:
  - url: https://api.atlas.kitchen/storefronts/v1
    description: Storefront API
security:
  - channelId: []
    sessionId: []
tags:
  - name: Introduction
    description: >
      The Atlas Storefront API powers online ordering for food and beverage
      merchants. Each merchant on Atlas operates one or more **outlets**
      (physical locations), each with its own menus, service hours, delivery
      zones, and payment processing.


      This API lets you build a complete ordering experience on top of Atlas:


      - **Browse** a merchant's menus with sections, items, modifiers, and
      real-time stock

      - **Build** a cart with configurable items, promo codes, and loyalty
      points

      - **Pay** via a hosted payment page with real-time confirmation

      - **Confirm** orders with a single API call after payment


      ### How it works


      Every merchant has a **channel** — a unique identifier representing their
      storefront (website, app, kiosk, etc.). The channel determines which
      outlets, menus, and branding the diner sees. You receive a channel ID when
      a merchant is onboarded.


      The API is stateless from your perspective. You pass the channel ID, the
      API gives you a session, and from there you can browse menus, build a
      cart, and check out — all without user accounts or login.


      ### Conventions


      **Monetary values** — All monetary fields (`price_cents`, `subtotal`,
      `total`, `tax`, `delivery_fee`, etc.) are integers in **minor currency
      units** (e.g. cents). `1580` = $15.80 SGD.


      **Timeslots** — Time values (`timeslot_start`, `timeslot_end`,
      `service_start`, `service_end`) are in **seconds since midnight**. `36000`
      = 10:00 AM.


      **Errors** — All errors follow a consistent structure:

      ```json

      {
        "type": "Invalid Parameter",
        "message": "Human-readable description",
        "details": [
          { "field": "item_id", "message": "must exist" }
        ]
      }

      ```
  - name: Authentication
    description: >
      ### Channel ID


      Every request must include an `X-Channel-Id` header. This identifies which
      merchant's storefront you're accessing — it determines the outlets, menus,
      branding, and payment configuration the diner sees.


      You receive the channel ID when a merchant is onboarded to Atlas. It is a
      fixed identifier, not a secret.


      ### Sessions


      The API automatically creates a session on your first request and returns
      it in the `X-Session-Id` response header. This session tracks the diner's
      cart.


      ```bash

      # First request — only X-Channel-Id needed

      curl -sD - https://api.example.com/storefronts/v1/channel \
        -H "X-Channel-Id: your-channel-id"
      ```


      ```

      # Response header — save this

      X-Session-Id: b7ac755d301dc5d23c790ccd7bb6dadf

      ```


      Include both headers on all subsequent requests:


      | Header | What it is | Where it comes from |

      |--------|-----------|-------------------|

      | `X-Channel-Id` | Merchant's storefront identifier | Provided during
      onboarding |

      | `X-Session-Id` | Diner's session (tracks cart) | Auto-created, returned
      in first response header |


      Sessions are lightweight — they hold a reference to the diner's active
      cart. When an order is placed, the session gets a new empty cart
      automatically.
  - name: Checkout Flow
    description: >
      The complete ordering flow from menu to confirmed order:


      <img src="/checkout-flow.svg" alt="Checkout Flow"
      style="width:100%;max-width:100%" />


      ### After payment


      Once the diner completes payment on the hosted page and is redirected back
      to your app, call `POST /cart/order`.


      This endpoint validates the payment **in real-time** — it makes a
      synchronous call to the payment processor to check status. It does **not**
      depend on webhooks.


      - **Payment succeeded** → cart converts to a confirmed order, full order
      object returned

      - **Payment not completed** → `422` with `"Insufficient payment made to
      the cart"`


      No polling needed. No webhook waiting. Just one call.
  - name: Channel
    description: >
      Retrieve merchant configuration, outlet details, service hours, delivery
      fees, and content (announcements, banners, popups).


      Start here — `GET /channel` gives you the outlets you can order from,
      including their coordinates, service hours, and delivery fee structure.
  - name: Menu
    description: >
      Browse menus for a specific outlet and serving date. Returns the full
      catalog: sections, items with prices, configurable items with modifier
      groups, stock levels, timeslots, and availability.


      ### How menus are structured


      ```

      Menu

      ├── sections[]           → categories (Mains, Sides, Drinks)

      │   ├── products[]       → item IDs + display_order

      │   └── sub_sections[]   → nested categories

      └── products{}           → full item details keyed by ID
          ├── price_cents
          ├── is_configurable
          └── item_modifier_groups[]  → modifier groups with options
      ```


      Use `sections[].products[].id` to look up full item details in the
      `products{}` map.
  - name: Cart
    description: >
      Manage the shopping cart — create, configure, add/remove items, apply
      promos, and review pricing.


      ### Cart lifecycle


      1. `POST /cart` — create empty cart

      2. `PATCH /cart` — set outlet, fulfilment type, timeslot, contact details

      3. `POST /cart/items` — add items (with optional modifiers via
      `sub_items`)

      4. `GET /cart/payment_breakdown` — review subtotal, tax, fees, discounts

      5. `POST /cart/validation` — verify cart is ready for checkout


      ### Adding items with modifiers


      For configurable items (`is_configurable: true`), pass modifiers as
      `sub_items`:


      ```json

      {
        "item_id": 1,
        "quantity": 2,
        "sub_items": [
          {
            "item_id": 2,
            "modifier_id": 1,
            "item_modifier_group_id": 1,
            "quantity": 1
          }
        ]
      }

      ```


      Get `modifier_id` and `item_modifier_group_id` from the menu's
      `item_modifier_groups`.
  - name: Payment
    description: >
      Create a payment intent to generate a hosted payment page URL.


      ### Flow


      1. Call `POST /payment_intents` → receive `atlas_pay_url`

      2. Redirect the diner to `atlas_pay_url`

      3. Diner enters payment details and pays

      4. Diner is redirected back to your app

      5. Call `POST /cart/order` to confirm (see **Order**)


      If a pending payment intent already exists for the same cart amount, the
      existing one is returned instead of creating a duplicate.
  - name: Order
    description: >
      Convert a paid cart into a confirmed order.


      Call `POST /cart/order` after the diner completes payment. This endpoint:


      1. Validates the cart (items, timeslot, minimum order)

      2. Checks payment status **in real-time** against the payment processor

      3. Converts the cart to a confirmed order


      On success, returns the full order object. The session automatically gets
      a fresh empty cart for the next order.


      If called before payment completes, returns `422` with a clear error
      message.
paths:
  /cart/order:
    post:
      tags:
        - Order
      summary: Create order
      description: >
        **Call this after the diner completes payment and is redirected back.**


        This endpoint does three things in sequence:

        1. Validates the cart (items, timeslot, minimum order, etc.)

        2. Checks payment status **in real-time** against the payment processor
           (does NOT depend on webhooks — makes a synchronous call to verify)
        3. If fully paid, converts the cart to a confirmed order


        Returns the full order object on success, or `422` with specific errors

        if the cart is invalid or payment hasn't completed yet.


        If the order was already created (e.g. duplicate call), returns the
        existing order.


        After success, the session automatically gets a new empty cart.
      operationId: createOrder
      responses:
        '200':
          description: Order created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
              example:
                id: 1
                identifier: '3'
                channel_id: 1
                outlet_id: 1
                brand_id: 1
                brand:
                  name: Aviato Burger
                fulfilment_type: delivery
                state: confirmed
                call_number: null
                notes: null
                serving_date: '2026-04-17'
                timeslot_start: 34200
                timeslot_end: 36000
                timeslot_range: 9:30AM–10:00AM
                timeslot_type: available_timeslots
                contact_name: Jane Doe
                contact_email: jane@example.com
                contact_number: '+6591234567'
                address_line1: 123 Orchard Road
                address_line2: '#04-56'
                address_latitude: 1.3021
                address_longitude: 103.8198
                postal_code: '238858'
                is_cutlery_required: false
                is_contactless: false
                is_asap: false
                is_gift: false
                recipient_name: null
                recipient_contact_number: null
                recipient_organisation_name: null
                gift_message: null
                is_paid: true
                promo_code: null
                confirmation_custom_message: null
                payment_breakdown:
                  minimum_order_value: null
                  donation_amount: 0
                  cash_vouchers_amount: 0
                  service_charge: 0
                  delivery_fee: 600
                  amount_to_free_delivery: null
                  amount_to_next_delivery_fee: null
                  delivery_fee_waiver_cart_subtotal: null
                  surcharge: 0
                  surcharge_label: null
                  use_points: false
                  points_amount: 0
                  points_value: 0
                  discount: 0
                  admin_discount: 0
                  subtotal: 1890
                  total: 2490
                  total_including_tax: 2490
                  tax: 0
                  tax_inclusive_prices: false
                  amount_paid: 2490
                  amount_unpaid: 0
                  is_post_tax_discount: false
                claim_token_url: null
                eligible_points: 0
                eligible_points_value: 0
                created_at: 1776534900
                updated_at: 1776534901
                cancelled_at: null
                completed_at: null
                order_items:
                  - id: 1
                    item_id: 1
                    name: Scrambled Eggs Bowl
                    quantity: 1
                    currency: SGD
                    price_cents: 790
                    discount: 0
                    calculated_subtotal: 840
                    per_unit_quantity: 1
                    unit_label: null
                    notes: Extra napkins please
                    cart_item_id: 1
                    item_modifier_group_id: null
                    modifier_id: null
                    sub_items:
                      - id: 2
                        item_id: 2
                        name: Mild
                        quantity: 1
                        currency: SGD
                        price_cents: 0
                        discount: 0
                        calculated_subtotal: 0
                        per_unit_quantity: 1
                        unit_label: null
                        notes: null
                        cart_item_id: 2
                        item_modifier_group_id: 1
                        modifier_id: 1
                        sub_items: []
                      - id: 3
                        item_id: 4
                        name: Bacon
                        quantity: 1
                        currency: SGD
                        price_cents: 0
                        discount: 0
                        calculated_subtotal: 0
                        per_unit_quantity: 1
                        unit_label: null
                        notes: null
                        cart_item_id: 3
                        item_modifier_group_id: 2
                        modifier_id: 3
                        sub_items: []
                      - id: 4
                        item_id: 13
                        name: Extra Cheese
                        quantity: 1
                        currency: SGD
                        price_cents: 50
                        discount: 0
                        calculated_subtotal: 50
                        per_unit_quantity: 1
                        unit_label: null
                        notes: null
                        cart_item_id: 4
                        item_modifier_group_id: 4
                        modifier_id: 12
                        sub_items: []
                  - id: 5
                    item_id: 17
                    name: Corn Chips
                    quantity: 2
                    currency: SGD
                    price_cents: 400
                    discount: 0
                    calculated_subtotal: 800
                    per_unit_quantity: 1
                    unit_label: null
                    notes: null
                    cart_item_id: 5
                    item_modifier_group_id: null
                    modifier_id: null
                    sub_items: []
                  - id: 6
                    item_id: 22
                    name: Water
                    quantity: 1
                    currency: SGD
                    price_cents: 250
                    discount: 0
                    calculated_subtotal: 250
                    per_unit_quantity: 1
                    unit_label: null
                    notes: null
                    cart_item_id: 6
                    item_modifier_group_id: null
                    modifier_id: null
                    sub_items: []
                order_payments:
                  - id: 1
                    capture_id: ab2c8505-c7bc-40ea-9f54-97774ce347b5
                    is_captured: true
                    capture_type: atlas_pay
                    payment_type_id: 2
                    amount: 2490
        '422':
          description: |
            Cart not ready for checkout. Common reasons:
            - `payment`: Payment not completed or insufficient amount
            - `cart_items`: Cart is empty
            - `timeslot_start`: Timeslot not set
components:
  schemas:
    Order:
      type: object
      properties:
        id:
          type: integer
        identifier:
          type: string
        channel_id:
          type: integer
        outlet_id:
          type: integer
        brand_id:
          type: integer
        brand:
          $ref: '#/components/schemas/Brand'
        fulfilment_type:
          type: string
        state:
          type: string
        call_number:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        serving_date:
          type: string
          format: date
        timeslot_start:
          type: integer
        timeslot_end:
          type: integer
        timeslot_range:
          type: string
        timeslot_type:
          type: string
        contact_name:
          type: string
        contact_email:
          type: string
        contact_number:
          type: string
        address_line1:
          type: string
          nullable: true
        address_line2:
          type: string
          nullable: true
        address_latitude:
          type: number
          format: double
          nullable: true
        address_longitude:
          type: number
          format: double
          nullable: true
        postal_code:
          type: string
          nullable: true
        is_cutlery_required:
          type: boolean
        is_contactless:
          type: boolean
        is_asap:
          type: boolean
        is_gift:
          type: boolean
        recipient_name:
          type: string
          nullable: true
        recipient_contact_number:
          type: string
          nullable: true
        recipient_organisation_name:
          type: string
          nullable: true
        gift_message:
          type: string
          nullable: true
        is_paid:
          type: boolean
        promo_code:
          type: string
          nullable: true
        confirmation_custom_message:
          type: string
          nullable: true
        payment_breakdown:
          $ref: '#/components/schemas/PaymentBreakdown'
        claim_token_url:
          type: string
          nullable: true
        eligible_points:
          type: integer
        eligible_points_value:
          type: integer
        created_at:
          type: integer
          description: Unix timestamp
        updated_at:
          type: integer
          description: Unix timestamp
        cancelled_at:
          type: integer
          description: Unix timestamp
          nullable: true
        completed_at:
          type: integer
          description: Unix timestamp
          nullable: true
        order_items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        order_payments:
          type: array
          items:
            $ref: '#/components/schemas/OrderPayment'
    Brand:
      type: object
      nullable: true
      properties:
        name:
          type: string
    PaymentBreakdown:
      type: object
      properties:
        minimum_order_value:
          type: integer
          nullable: true
        donation_amount:
          type: integer
        cash_vouchers_amount:
          type: integer
        service_charge:
          type: integer
        delivery_fee:
          type: integer
        amount_to_free_delivery:
          type: integer
          nullable: true
        amount_to_next_delivery_fee:
          type: integer
          nullable: true
        delivery_fee_waiver_cart_subtotal:
          type: integer
          nullable: true
        surcharge:
          type: integer
        surcharge_label:
          type: string
          nullable: true
        use_points:
          type: boolean
        points_amount:
          type: integer
        points_value:
          type: integer
        discount:
          type: integer
        admin_discount:
          type: integer
        is_post_tax_discount:
          type: boolean
        subtotal:
          type: integer
        total:
          type: integer
        total_including_tax:
          type: integer
        tax:
          type: integer
        tax_inclusive_prices:
          type: boolean
        amount_paid:
          type: integer
          nullable: true
        amount_unpaid:
          type: integer
          nullable: true
    OrderItem:
      type: object
      properties:
        id:
          type: integer
        item_id:
          type: integer
        name:
          type: string
        quantity:
          type: integer
        currency:
          type: string
        price_cents:
          type: integer
        discount:
          type: integer
        calculated_subtotal:
          type: integer
        per_unit_quantity:
          type: integer
        unit_label:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        cart_item_id:
          type: integer
        item_modifier_group_id:
          type: integer
          nullable: true
        modifier_id:
          type: integer
          nullable: true
        sub_items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
    OrderPayment:
      type: object
      properties:
        id:
          type: integer
        capture_id:
          type: string
        is_captured:
          type: boolean
        capture_type:
          type: string
        payment_type_id:
          type: integer
        amount:
          type: integer
  securitySchemes:
    channelId:
      type: apiKey
      in: header
      name: X-Channel-Id
      description: Merchant storefront identifier. Provided during onboarding.
    sessionId:
      type: apiKey
      in: header
      name: X-Session-Id
      description: >-
        Diner session identifier. Created by `GET /channel` and returned in the
        `X-Session-Id` response header.

````