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

# Create order

> Creates an order and returns a Paystack payment link. The order is saved
with source `storefront` and appears in the suite as **Website**.

Send the customer's choices only. Leyyow validates stock, then calculates
unit prices, tax, delivery fee, and total server-side.

### Fulfilment field combinations

The delivery fields must be sent as a matching set:

| Customer choice | `fulfilment_method` | `delivery_method` | `manual_delivery_option` | `rate` | `courier` |
|---|---|---|---|---|---|
| Pickup | `pickup` | `null` | `null` | `""` | `{}` |
| Manual or Express | `delivery` | `manual` | option `uid` | `""` | `{}` |
| Managed (ShipBubble) | `delivery` | `shipbubble` | `null` | quote `request_token` | chosen `Courier` |

After payment, Paystack returns the customer to the Checkout Return URL
saved in the suite.




## OpenAPI

````yaml /openapi.yaml post /api/public/v1/orders
openapi: 3.1.0
info:
  title: Leyyow Public Commerce API
  version: 1.0.0
  summary: Build a custom storefront on top of a Leyyow store.
  description: >
    The Public Commerce API lets a merchant's own website or application read
    their

    Leyyow catalog, calculate delivery, create an order, and receive a Paystack

    payment link.


    ## Authentication


    Every request requires an API key created in the Leyyow suite under

    **Settings → Public API**, sent as a bearer token:


    ```

    Authorization: Bearer lyw_live_xxxxxxxxxxxxxxxx

    ```


    The key identifies exactly one store. Any product, variant, delivery option,

    shipping quote, or order that does not belong to that store returns `404`,

    regardless of whether the identifier exists elsewhere in Leyyow.


    Keys used in browser code are visible to anyone who opens the network

    inspector. Keep the key server-side where possible, and rotate it from the

    suite if it is abused.


    ## Response envelope


    Every response — success or failure — uses the same envelope:


    ```json

    { "error": null, "message": "Products retrieved.", "data": {} }

    ```


    On success `error` is `null` and `data` carries the payload. On failure

    `error` holds a human-readable reason, `data` is `null`, and the HTTP status

    carries the machine-readable meaning.


    ## Server-calculated amounts


    Prices, taxes, delivery fees, and totals are calculated by Leyyow. Order

    requests carry the customer's *choices* — variants, quantities, fulfilment

    method — never amounts. Any amount submitted in a request body is ignored.


    ## Rate limits


    Order creation and shipping quotes are rate limited more strictly than

    catalog reads. Exceeding a limit returns `429`.
  contact:
    name: Leyyow API support
    email: support@leyyow.com
servers:
  - url: https://sandbox.api.leyyow.com
    description: Sandbox — test keys, seeded data, no real orders
  - url: https://api.leyyow.com
    description: Production — live merchant data
security:
  - bearerAuth: []
tags:
  - name: Store
    description: Store profile, theme, categories, and fulfilment configuration.
  - name: Products
    description: Customer-facing catalog.
  - name: Delivery
    description: Fulfilment options and managed-delivery quotes.
  - name: Orders
    description: Order creation and status.
paths:
  /api/public/v1/orders:
    post:
      tags:
        - Orders
      summary: Create order
      description: >
        Creates an order and returns a Paystack payment link. The order is saved

        with source `storefront` and appears in the suite as **Website**.


        Send the customer's choices only. Leyyow validates stock, then
        calculates

        unit prices, tax, delivery fee, and total server-side.


        ### Fulfilment field combinations


        The delivery fields must be sent as a matching set:


        | Customer choice | `fulfilment_method` | `delivery_method` |
        `manual_delivery_option` | `rate` | `courier` |

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

        | Pickup | `pickup` | `null` | `null` | `""` | `{}` |

        | Manual or Express | `delivery` | `manual` | option `uid` | `""` | `{}`
        |

        | Managed (ShipBubble) | `delivery` | `shipbubble` | `null` | quote
        `request_token` | chosen `Courier` |


        After payment, Paystack returns the customer to the Checkout Return URL

        saved in the suite.
      operationId: createOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            examples:
              manualDelivery:
                summary: Manual delivery
                value:
                  customer_name: Ada Okafor
                  customer_email: ada@example.com
                  customer_phone: '+2348012345678'
                  items:
                    - variant: 550e8400-e29b-41d4-a716-446655440000
                      popup_inventory: null
                      quantity: 2
                      notes: ''
                  delivery_address: 12 Example Street, Lagos
                  delivery_method: manual
                  fulfilment_method: delivery
                  payment_source: paystack
                  courier: {}
                  rate: ''
                  manual_delivery_option: 7bf56f3d-20c2-4a52-b5d2-a1a98610e79f
              pickup:
                summary: Pickup
                value:
                  customer_name: Ada Okafor
                  customer_email: ada@example.com
                  customer_phone: '+2348012345678'
                  items:
                    - variant: 550e8400-e29b-41d4-a716-446655440000
                      popup_inventory: null
                      quantity: 1
                      notes: ''
                  delivery_address: null
                  delivery_method: null
                  fulfilment_method: pickup
                  payment_source: paystack
                  courier: {}
                  rate: ''
                  manual_delivery_option: null
      responses:
        '201':
          description: Order created.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/CreateOrderResult'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/StockConflict'
        '410':
          $ref: '#/components/responses/QuoteExpired'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  schemas:
    CreateOrderRequest:
      type: object
      required:
        - customer_name
        - customer_email
        - customer_phone
        - items
        - fulfilment_method
        - payment_source
      properties:
        customer_name:
          type: string
          examples:
            - Ada Okafor
        customer_email:
          type: string
          format: email
        customer_phone:
          type: string
          examples:
            - '+2348012345678'
        items:
          type: array
          minItems: 1
          items:
            type: object
            required:
              - variant
              - quantity
            properties:
              variant:
                type: string
                format: uuid
              popup_inventory:
                type:
                  - string
                  - 'null'
                description: Reserved. Send `null` — popup orders are out of scope in v1.
              quantity:
                type: integer
                minimum: 1
              notes:
                type: string
                description: Customer note for this line item.
        delivery_address:
          type:
            - string
            - 'null'
          description: Required when `fulfilment_method` is `delivery`.
        delivery_method:
          type:
            - string
            - 'null'
          enum:
            - manual
            - shipbubble
            - null
          description: |
            `manual` for Manual or Express Delivery, `shipbubble` for Managed
            Delivery, `null` for pickup.
        fulfilment_method:
          type: string
          enum:
            - pickup
            - delivery
        payment_source:
          type: string
          enum:
            - paystack
        courier:
          type: object
          additionalProperties: true
          description: |
            The full `Courier` object from the shipping quote. Send `{}` for
            pickup and manual delivery.
        rate:
          type: string
          description: >
            The `request_token` from the shipping quote. Send `""` for pickup
            and

            manual delivery.
        manual_delivery_option:
          type:
            - string
            - 'null'
          format: uuid
          description: |
            `uid` of the chosen delivery option. Required when `delivery_method`
            is `manual`, otherwise `null`.
    Envelope:
      type: object
      required:
        - error
        - message
        - data
      properties:
        error:
          type:
            - string
            - 'null'
          description: Human-readable failure reason, or `null` on success.
          examples:
            - null
        message:
          type: string
          description: Short status message.
          examples:
            - Request successful.
        data:
          description: Response payload. `null` on failure.
    CreateOrderResult:
      type: object
      properties:
        order:
          $ref: '#/components/schemas/Order'
        payment:
          type: object
          properties:
            payment_link:
              type: string
              format: uri
              description: |
                Paystack checkout URL. Redirect the customer here. Paystack
                returns them to the Checkout Return URL configured in the suite.
    Order:
      type: object
      properties:
        uid:
          type: string
          format: uuid
        order_number:
          type: string
          examples:
            - LYW-10482
        source:
          type: string
          const: storefront
          description: Always `storefront` for API-created orders.
        store:
          type: string
          format: uuid
        store_name:
          type: string
        store_email:
          type: string
          format: email
        store_phone:
          type: string
        logo:
          type: string
          format: uri
        location:
          type: string
          format: uuid
        location_name:
          type: string
        customer:
          type: string
          format: uuid
        customer_name:
          type: string
        customer_phone:
          type: string
        customer_email:
          type: string
          format: email
        customer_address:
          type: string
        user:
          type:
            - string
            - 'null'
        total_amount:
          $ref: '#/components/schemas/Money'
        subtotal:
          $ref: '#/components/schemas/Money'
        discount_amount:
          $ref: '#/components/schemas/Money'
        tax_amount:
          $ref: '#/components/schemas/Money'
        tax_rate_used:
          type: string
          examples:
            - '7.50'
        total_paid:
          type: number
        outstanding_balance:
          type: number
        payment_status:
          type: string
          examples:
            - pending
        payment_status_display:
          type: string
          examples:
            - Pending
        fulfilment_status:
          type: string
        fulfilment_status_display:
          type: string
        fulfilment_method:
          type: string
          enum:
            - pickup
            - delivery
        delivery_fee:
          $ref: '#/components/schemas/Money'
        manual_delivery_type:
          type:
            - string
            - 'null'
        delivery_address:
          type:
            - string
            - 'null'
        delivery_method:
          type:
            - string
            - 'null'
        courier:
          type: object
          additionalProperties: true
        rate:
          type: string
        tracking_number:
          type: string
        has_shipping:
          type: boolean
        order_date:
          type:
            - string
            - 'null'
          format: date-time
        shipping_details:
          type:
            - object
            - 'null'
          additionalProperties: true
        coupon:
          type:
            - string
            - 'null'
          description: Always `null` in v1 — coupons are out of scope.
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        created_at:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required:
        - error
        - message
        - data
      properties:
        error:
          type: string
          description: Human-readable failure reason.
        message:
          type: string
        data:
          type: 'null'
    Money:
      type: string
      description: |
        Decimal amount as a string, in the store's currency (NGN). Kept as a
        string to avoid floating-point rounding. Example — `"45000.00"`.
      examples:
        - '45000.00'
    OrderItem:
      type: object
      properties:
        uid:
          type: string
          format: uuid
        variant:
          type: string
          format: uuid
        variant_name:
          type: string
        variant_sku:
          type: string
        product_name:
          type: string
        image:
          type: string
          format: uri
        popup_inventory:
          type:
            - string
            - 'null'
        quantity:
          type: integer
        unit_price:
          $ref: '#/components/schemas/Money'
        total_price:
          $ref: '#/components/schemas/Money'
        fulfilment_status:
          type: string
        qty_fulfilled:
          type: integer
        notes:
          type: string
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/Attribute'
        product_images:
          type: array
          items:
            $ref: '#/components/schemas/ProductImage'
    Attribute:
      type: object
      properties:
        uid:
          type: string
          format: uuid
        attribute:
          type: string
          format: uuid
        value:
          type: string
          format: uuid
        attribute_name:
          type: string
          examples:
            - Size
        attribute_value:
          type: string
          examples:
            - Medium
    ProductImage:
      type: object
      properties:
        uid:
          type: string
          format: uuid
        image:
          type: string
          format: uri
        alt_text:
          type: string
        is_primary:
          type: boolean
        order:
          type: integer
        sort_order:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
  responses:
    ValidationError:
      description: Required field missing or field value invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: manual_delivery_option is required when delivery_method is manual.
            message: Order could not be created.
            data: null
    Unauthorized:
      description: API key missing, malformed, or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Invalid or revoked API key.
            message: Authentication failed.
            data: null
    NotFound:
      description: |
        Record not found, or not owned by the store linked to this API key.
        Identifiers from another store return `404` rather than `403`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Product not found.
            message: The requested resource does not exist.
            data: null
    StockConflict:
      description: One or more items no longer have enough sellable stock.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Insufficient sellable stock for one or more items.
            message: Order could not be created.
            data: null
    QuoteExpired:
      description: |
        The shipping quote token has expired or does not match the submitted
        address and items. Request a fresh quote.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Shipping quote has expired.
            message: Order could not be created.
            data: null
    RateLimited:
      description: Too many requests. Retry after the window resets.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Rate limit exceeded.
            message: Too many requests.
            data: null
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Store API key created in the Leyyow suite under Settings → Public API.
        Sent as `Authorization: Bearer <key>`.

````