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

# Create a checkout session

> Start a Laces-hosted checkout for one event and get the URL to send the buyer to.



## OpenAPI

````yaml api-reference/v1/openapi.json POST /checkout-sessions
openapi: 3.1.0
info:
  title: Laces Partner API
  version: 1.0.0
  summary: Show your organization's races and sell entries from your own website.
  description: >-
    The Laces Partner API, served at `https://connect.laces.run/v1`. Every path
    is relative to that server URL, which already ends in `/v1`.


    Authenticate with a **bearer token** from `POST /oauth/token` (server side)
    or, on the routes that allow it, a **publishable key** in
    `X-Laces-Publishable-Key` (browser side, from an allowed domain). Keys are
    uppercase and case-sensitive: send them exactly as issued.


    There is one environment, production. Payments made through the API are
    real.


    Money is integer minor units. Every route except the token endpoint answers
    in the Laces envelope `{ code, message, data | error, error_code }`; branch
    on `error_code`, never on `message` or `error`.


    Every response carries an `X-Request-Id` header (`req_…`). Quote it to
    support@laces.run when something goes wrong. Browsers can read
    `X-Request-Id` and `Retry-After` (both are CORS-exposed). Rate-limited
    routes also send the standard `RateLimit-Limit`, `RateLimit-Remaining` and
    `RateLimit-Reset` headers.


    Partner credentials are accepted on this host only. Sent to `api.laces.run`
    (the Laces app's own API), any partner credential is refused with `401
    partner_credential_not_accepted`.
  contact:
    name: Laces support
    email: support@laces.run
    url: https://docs.laces.run
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
  - url: https://connect.laces.run/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: OAuth 2.0 client credentials.
  - name: Events
    description: Your organization's races that are on sale.
  - name: Checkout sessions
    description: 'Laces-hosted checkout: redirect or embedded.'
  - name: Orders
    description: Create, pay and read orders from your own UI.
  - name: Webhook events
    description: Requests Laces sends to your webhook endpoint.
  - name: Health
    description: Liveness probe.
paths:
  /checkout-sessions:
    post:
      tags:
        - Checkout sessions
      summary: Create a checkout session
      description: >-
        Start a Laces-hosted checkout for one event. Returns the `url` to send
        the buyer to (`redirect`) or the session that laces.js opens in a modal
        (`embedded`). Sessions expire after 24 hours.


        Works with a publishable key from an allowed domain, or a bearer token.
        Scope `checkout_sessions:write`.


        - **Return URLs**: `on_success_url` and `on_cancel_url` are required for
        `redirect` and optional for `embedded`. Each must be an absolute `https`
        URL of at most 2048 characters whose origin exactly matches one of your
        **return URL domains** (`http://localhost:<port>` and
        `http://127.0.0.1:<port>` are accepted when you registered them). Put
        `{CHECKOUT_SESSION_ID}` (or its URL-encoded form
        `%7BCHECKOUT_SESSION_ID%7D`) in `on_success_url` to receive the id; with
        no placeholder, `session_id=<id>` is added as a query parameter,
        replacing any `session_id` already there.

        - **Embedded**: with a publishable key, the request's `Origin` becomes
        the one page allowed to frame the checkout. With a bearer token, send
        `embed_origin`, which must be one of your allowed domains.

        - **Clearance and sale window** are checked up front: no session is
        created for an event that is not on sale (`409 sale_closed`) or an
        organization that cannot take payments (`403 payments_disabled`).


        **Publishable-key calls fail from this playground** (`403
        origin_not_allowed`): they need a browser `Origin` on your allowed
        domains. Use a bearer token here.
      operationId: createCheckoutSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCheckoutSessionRequest'
            examples:
              redirect:
                summary: Hosted checkout (redirect)
                value:
                  event_id: q3Zk9mVx
                  ui_mode: redirect
                  on_success_url: >-
                    https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}
                  on_cancel_url: https://shop.example.com/events/kampala-city-10k
                  client_reference_id: cart-8841
                  prefill:
                    buyer_name: Amina Nakato
                    buyer_email: amina@example.com
              embedded:
                summary: Embedded checkout, server-created
                value:
                  event_id: q3Zk9mVx
                  ui_mode: embedded
                  embed_origin: https://shop.example.com
                  client_reference_id: cart-8841
      responses:
        '201':
          description: The session.
          content:
            application/json:
              schema:
                type: object
                required:
                  - code
                  - message
                  - data
                properties:
                  code:
                    type: integer
                    description: HTTP status, repeated in the body.
                    examples:
                      - 201
                  message:
                    type: string
                    examples:
                      - Checkout session created successfully
                  data:
                    $ref: '#/components/schemas/CheckoutSessionCreated'
              example:
                code: 201
                message: Checkout session created successfully
                data:
                  id: LCSCS-EXAMPLESESSN234EXAMPL
                  url: https://laces.run/checkout/LCSCS-EXAMPLESESSN234EXAMPL
                  expires_at: '2026-10-10T08:14:03.000Z'
                  ui_mode: redirect
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
        '400':
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid_request:
                  summary: invalid_request
                  value:
                    code: 400
                    message: Bad request
                    error: event_id is required
                    error_code: invalid_request
                invalid_ui_mode:
                  summary: invalid_ui_mode
                  value:
                    code: 400
                    message: Bad request
                    error: ui_mode must be redirect or embedded
                    error_code: invalid_ui_mode
                invalid_return_url:
                  summary: invalid_return_url
                  value:
                    code: 400
                    message: Bad request
                    error: on_success_url origin is not in your return URL domains
                    error_code: invalid_return_url
                invalid_embed_origin:
                  summary: invalid_embed_origin
                  value:
                    code: 400
                    message: Bad request
                    error: embed_origin is not in your allowed domains
                    error_code: invalid_embed_origin
                invalid_json:
                  summary: invalid_json
                  value:
                    code: 400
                    message: Validation failed
                    error: The request body is not valid JSON
                    error_code: invalid_json
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            The credential is not allowed here, or the organization cannot take
            payments yet.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                insufficient_scope:
                  summary: insufficient_scope
                  value:
                    code: 403
                    message: Forbidden
                    error: 'Missing scope: checkout_sessions:write'
                    error_code: insufficient_scope
                origin_not_allowed:
                  summary: origin_not_allowed
                  value:
                    code: 403
                    message: Forbidden
                    error: This origin is not allowed for this key
                    error_code: origin_not_allowed
                payments_disabled:
                  summary: payments_disabled
                  value:
                    code: 403
                    message: Request failed
                    error: This organizer is not set up to accept payments yet
                    error_code: payments_disabled
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Registration is not open.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                sale_closed:
                  summary: sale_closed
                  value:
                    code: 409
                    message: Conflict
                    error: Registration is closed
                    error_code: sale_closed
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
      security:
        - bearerAuth: []
        - oauth2:
            - checkout_sessions:write
        - publishableKey: []
components:
  schemas:
    CreateCheckoutSessionRequest:
      type: object
      properties:
        event_id:
          type: string
          description: Your organization's event.
          examples:
            - q3Zk9mVx
        ui_mode:
          type: string
          description: >-
            `redirect` (hosted page, the default) or `embedded` (laces.js
            modal).
          enum:
            - redirect
            - embedded
          default: redirect
        on_success_url:
          type: string
          description: >-
            Required for `redirect`, optional for `embedded`. Absolute `https`
            URL whose origin exactly matches one of your return URL domains
            (`http://localhost:<port>` / `http://127.0.0.1:<port>` only when
            registered). `{CHECKOUT_SESSION_ID}` (or
            `%7BCHECKOUT_SESSION_ID%7D`) is replaced with the session id; with
            no placeholder, `session_id=<id>` is added as a query parameter,
            overwriting any existing `session_id`.
          maxLength: 2048
          examples:
            - https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}
        on_cancel_url:
          type: string
          description: >-
            Required for `redirect`, optional for `embedded`. Same rules; used
            as given (no id added).
          format: uri
          maxLength: 2048
          examples:
            - https://shop.example.com/events/kampala-city-10k
        embed_origin:
          type: string
          description: >-
            Embedded sessions created with a **bearer token** only: the origin
            (`https://host[:port]`) of the page that will host the modal. Must
            be one of your allowed domains (`400 invalid_embed_origin`
            otherwise). With a publishable key the request's `Origin` is used
            and this field is ignored.
          examples:
            - https://shop.example.com
        client_reference_id:
          type: string
          description: >-
            Your reference, returned when you read the session. Match it on
            verification.
          maxLength: 256
          examples:
            - cart-8841
        prefill:
          type: object
          properties:
            buyer_name:
              type: string
              maxLength: 120
            buyer_email:
              type: string
              format: email
              maxLength: 254
            buyer_phone:
              type: string
              maxLength: 32
          description: Optional values to pre-fill on the checkout form.
      required:
        - event_id
    CheckoutSessionCreated:
      type: object
      properties:
        id:
          type: string
          examples:
            - LCSCS-EXAMPLESESSN234EXAMPL
        url:
          type: string
          description: >-
            The Laces-hosted checkout page. Redirect the buyer here (or let
            laces.js open it).
          format: uri
        expires_at:
          type: string
          description: >-
            24 hours after creation. The session is deleted some time after
            this, so a later read can be `404`.
          format: date-time
        ui_mode:
          type: string
          enum:
            - redirect
            - embedded
      required:
        - id
        - url
        - expires_at
        - ui_mode
    Error:
      type: object
      properties:
        code:
          type: integer
          description: HTTP status, repeated in the body.
        message:
          type: string
          description: Short outcome. Do not branch on it; it may be reworded.
        error:
          type: string
          description: Human-readable detail. May be reworded; never parse it.
        error_code:
          type: string
          description: >-
            Stable machine-readable code. **Branch on this.** Present on every
            Partner API error, including `429 rate_limited`, `404 not_found` for
            unknown routes and `500 server_error`. See the [error
            reference](/concepts/errors).
        details:
          type: object
          properties:
            retry_after_seconds:
              type: integer
          description: Only on `409 prompt_in_progress`.
      required:
        - code
        - message
        - error
        - error_code
      description: The Laces error envelope.
  headers:
    X-Request-Id:
      description: >-
        Unique id of this request (`req_…`). On every response, readable from
        browsers. Quote it to support.
      schema:
        type: string
      example: req_V1StGXR8Z5jdHi6BmyTq
    Retry-After:
      description: Whole seconds to wait before retrying. Readable from browsers.
      schema:
        type: integer
    RateLimit-Limit:
      description: Requests allowed in the current window.
      schema:
        type: integer
    RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    RateLimit-Reset:
      description: Seconds until the window resets.
      schema:
        type: integer
  responses:
    Unauthorized:
      description: >-
        No credential, an invalid or expired token, or an invalid publishable
        key. Comes with `WWW-Authenticate: Bearer realm="laces-partner"`.
        Revoking or regenerating keys can take up to a minute to reach every
        server.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unauthorized:
              summary: unauthorized
              value:
                code: 401
                message: Unauthorized
                error: Missing credentials
                error_code: unauthorized
            invalid_token:
              summary: invalid_token
              value:
                code: 401
                message: Unauthorized
                error: Invalid or expired access token
                error_code: invalid_token
            invalid_key:
              summary: invalid_key
              value:
                code: 401
                message: Unauthorized
                error: Invalid publishable key
                error_code: invalid_key
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
    NotFound:
      description: >-
        Not found, or it belongs to another organization (the same answer, so
        existence never leaks).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            not_found:
              summary: not_found
              value:
                code: 404
                message: Not found
                error: Event not found
                error_code: not_found
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
    PayloadTooLarge:
      description: The body is over 1 MB.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            payload_too_large:
              summary: payload_too_large
              value:
                code: 413
                message: Validation failed
                error: The request body is too large
                error_code: payload_too_large
    RateLimited:
      description: >-
        Rate limited. Wait `Retry-After` seconds. Limits are per API client
        (reads about 300 a minute, writes about 60) and, for publishable keys,
        also per IP (about 120 a minute).
      headers:
        Retry-After:
          $ref: '#/components/headers/Retry-After'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimit-Limit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimit-Remaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimit-Reset'
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            code: 429
            message: Too many requests, please slow down
            error: rate_limited
            error_code: rate_limited
    ServerError:
      description: >-
        Unexpected failure. Retry with backoff, reusing the same
        `Idempotency-Key`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            server_error:
              summary: server_error
              value:
                code: 500
                message: An unexpected error occurred
                error: Something went wrong
                error_code: server_error
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: LCSTOK-…-X (opaque)
      description: >-
        A token from `POST /oauth/token`, valid 1 hour. Paste it as `LCSTOK-…-X`
        (without the word Bearer).
    oauth2:
      type: oauth2
      description: >-
        OAuth 2.0 client credentials. The token it yields is the `bearerAuth`
        token.
      flows:
        clientCredentials:
          tokenUrl: https://connect.laces.run/v1/oauth/token
          scopes:
            events:read: List and read events
            orders:write: Create and pay orders
            orders:read: >-
              List and read orders, including buyer personal data (off by
              default)
            checkout_sessions:write: Create and read checkout sessions
    publishableKey:
      type: apiKey
      in: header
      name: X-Laces-Publishable-Key
      description: >-
        Browser-safe key `LCSPUBK-…-X`. Only works from a browser whose `Origin`
        is one of your allowed domains, and is capped to `events:read` and
        creating checkout sessions. **"Try it" with a publishable key fails in
        this playground** (`403 origin_not_allowed`): the request carries no
        allowed `Origin`. Use a bearer token here.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.