> ## 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 payment intent

> Validates the cart and creates a payment intent with the payment processor.
Returns an `atlas_pay_url` — redirect the diner there to complete payment.

If a pending intent already exists for the same amount, it is returned instead
of creating a duplicate.

**After payment:** Once the diner completes payment on the hosted page,
they are redirected back to your application. At that point, call
`POST /cart/order` to validate the payment and convert the cart to an order.
Do NOT wait for webhooks — `POST /cart/order` checks payment status in real-time.




## OpenAPI

````yaml post /payment_intents
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:
  /payment_intents:
    post:
      tags:
        - Payment
      summary: Create payment intent
      description: >
        Validates the cart and creates a payment intent with the payment
        processor.

        Returns an `atlas_pay_url` — redirect the diner there to complete
        payment.


        If a pending intent already exists for the same amount, it is returned
        instead

        of creating a duplicate.


        **After payment:** Once the diner completes payment on the hosted page,

        they are redirected back to your application. At that point, call

        `POST /cart/order` to validate the payment and convert the cart to an
        order.

        Do NOT wait for webhooks — `POST /cart/order` checks payment status in
        real-time.
      operationId: createPaymentIntent
      responses:
        '200':
          description: Payment intent with checkout URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentIntent'
              example:
                amount: 2490
                tip: 0
                status: pending
                atlas_pay_url: >-
                  https://pay.atlas.kitchen/intent/ab2c8505-c7bc-40ea-9f54-97774ce347b5
                currency: SGD
        '422':
          description: Cart validation failed
components:
  schemas:
    PaymentIntent:
      type: object
      properties:
        amount:
          type: integer
          description: Amount in cents
        tip:
          type: integer
        status:
          type: string
          enum:
            - pending
            - processing
            - capturing
            - succeeded
            - failed
            - cancelled
        atlas_pay_url:
          type: string
          nullable: true
          description: URL where the diner completes payment
        currency:
          type: string
          example: SGD
  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.

````