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

> ## Agent Instructions
> Notes on the Listo Partner Delivery API:
> - Every endpoint is under `https://api.listo.delivery/v1/` and authenticates with a bearer API key.
> - This documentation site is at https://docs.listo.delivery. The full machine-readable reference is the OpenAPI document at https://api.listo.delivery/docs/openapi.yaml (served by the API host, not by this site).
> - Any page of this site is also available as raw markdown by appending `.md` to its URL, for example https://docs.listo.delivery/en/guides/webhooks.md. The index of every page is https://docs.listo.delivery/llms.txt and all of them in one file is https://docs.listo.delivery/llms-full.txt.
> - Every page is published in English under https://docs.listo.delivery/en/ and in Spanish under https://docs.listo.delivery/es/, with the same path after the language. The API reference is under https://docs.listo.delivery/api-reference/ in English and https://docs.listo.delivery/es/api-reference/ in Spanish.
> - To debug an integration yourself: `GET /v1/request_logs` lists the calls your keys made with what you sent and what was answered, `GET /v1/events` lists the events Listo emitted, `GET /v1/webhook_deliveries` lists every attempt to deliver them, `POST /v1/events/{id}/resend` sends one event again and `POST /v1/test_webhooks` sends a sample event (`"test": true`). The page https://docs.listo.delivery/en/guides/debugging describes them. A resent event is a snapshot from when it happened; `GET /v1/deliveries` is still how you recover current state.
> - Every error response has `error.doc_url`, a page that explains that error code and what to do, and `error.request_id`, the same value as the `X-Request-Id` response header. All the pages are listed at https://docs.listo.delivery/en/errors. Filter `GET /v1/request_logs` with `request_id` to read the logged call.
> - A `409 account_exists` from `POST /v1/stores` means the restaurant already has its own Listo account: connect it instead of creating it, as the page https://docs.listo.delivery/en/guides/connect-existing-account describes.
> - Every live store carries one Plan Listo, charged to the partner. The exception is a connected restaurant that had already paid its current month: it keeps paying its own plan until its next renewal, shown as `paid_by: "store"` with the handover date in `pending_paid_by_on`. A `402 plan_required` from a quote, a delivery create or a retry means the store's plan is not paid or has ended; the page https://docs.listo.delivery/en/guides/plan-and-billing describes the plan, the `plan` block on a store and how to end or restart it.
> - An AI assistant (Claude Code, Cursor, Codex) can act through the same API key at `https://api.listo.delivery/mcp`, an MCP server over Streamable HTTP authenticated with `Authorization: Bearer <API key>`. Every tool runs the matching `/v1/` endpoint, and each one that moves money first previews and then needs a confirmation token; the page https://docs.listo.delivery/en/guides/connect-ai-client lists the tools and describes the two steps.

# Invite a store owner

> Gives the person who runs the store a login to its Listo console. We
create the login and email them a link to choose their own password.
The response carries the address the link went to and when it was
sent, and never a password or a token, so there is nothing in it to
store.

Repeating the call is safe. The same address gets a fresh link rather
than a second login, so a retry after a timeout does no harm, and it is
also how you send the link again when the first one has expired.
The link lasts seven days.

An address that already has a Listo login somewhere else is fine. That
person gets a separate login for this store, with its own password, and
the account they already had is left as it was. Somebody who runs two of
your locations can use the same address for both.

Three refusals:

- `422 console_access_required` on a store whose `access_tier` is
  `api_only`. That store has no console to sign in to, so there is
  nothing to invite anybody to. Set the tier first, then call this
  again.
- `422 validation_failed` with `param: "email"` when the store already
  has an owner at a different address. The `message` names that owner.
  A store has one owner, and this endpoint cannot change who it is. To
  send the owner a fresh link, call it with the address the `message`
  names.
- `422 validation_failed` with `param: "email"` when another request is
  setting up the same address for this store at the same moment. Repeat
  the call: by then the first one has finished, and yours sends the link
  again.




## OpenAPI

````yaml /openapi/en.yaml post /v1/stores/{store_id}/owner
openapi: 3.1.0
info:
  title: Listo Delivery API
  version: 1.34.0
  description: |
    Quote a delivery, dispatch it, and follow it to the door.

    Every request authenticates with a bearer API key. The key itself decides
    the world it acts in: a test key dispatches simulated couriers and moves no
    money, a live key dispatches real ones. A key can also be read-only: it
    may call GET and HEAD and nothing else, and any other call answers
    `403 read_only_key`. Money is always in Mexican pesos and
    always in integer cents, and every timestamp is ISO-8601 in UTC.

    Ids are opaque prefixed strings (`st_` a store, `qt_` a quote, `dlv_` a
    delivery, `wd_` a withdrawal, `evt_` an event, `le_` a ledger entry,
    `whd_` a webhook delivery attempt, `req_` a logged request). Treat
    them as strings of unbounded length and never parse them.

    Changes to v1 are additive: new fields and new enum values can appear, so
    ignore what you do not recognise rather than failing on it.
servers:
  - url: https://api.listo.delivery
security: []
tags:
  - name: Authentication
    description: Checking that a key works before you build against it.
  - name: Stores
    description: The pickup locations you dispatch from.
  - name: Account connection
    description: Connecting a restaurant's existing Listo account to you.
  - name: Quotes
    description: What a delivery costs, priced and held.
  - name: Deliveries
    description: Dispatching an order and following it to the door.
  - name: Balance
    description: The prepaid balance your deliveries draw on.
  - name: Withdrawals
    description: Moving money out of that balance to your bank.
  - name: Card onboarding
    description: Connecting the account that takes card payments.
  - name: Events
    description: >-
      The events Listo sent you, resending one, and sending a test event to your
      endpoint.
  - name: Webhook deliveries
    description: >-
      Every attempt Listo made to send an event to your endpoint, and what came
      back.
  - name: Request logs
    description: >-
      Every call your keys made to this API, with what you sent and what we
      answered.
paths:
  /v1/stores/{store_id}/owner:
    post:
      tags:
        - Stores
      summary: Invite a store owner
      description: |
        Gives the person who runs the store a login to its Listo console. We
        create the login and email them a link to choose their own password.
        The response carries the address the link went to and when it was
        sent, and never a password or a token, so there is nothing in it to
        store.

        Repeating the call is safe. The same address gets a fresh link rather
        than a second login, so a retry after a timeout does no harm, and it is
        also how you send the link again when the first one has expired.
        The link lasts seven days.

        An address that already has a Listo login somewhere else is fine. That
        person gets a separate login for this store, with its own password, and
        the account they already had is left as it was. Somebody who runs two of
        your locations can use the same address for both.

        Three refusals:

        - `422 console_access_required` on a store whose `access_tier` is
          `api_only`. That store has no console to sign in to, so there is
          nothing to invite anybody to. Set the tier first, then call this
          again.
        - `422 validation_failed` with `param: "email"` when the store already
          has an owner at a different address. The `message` names that owner.
          A store has one owner, and this endpoint cannot change who it is. To
          send the owner a fresh link, call it with the address the `message`
          names.
        - `422 validation_failed` with `param: "email"` when another request is
          setting up the same address for this store at the same moment. Repeat
          the call: by then the first one has finished, and yours sends the link
          again.
      operationId: createStoreOwner
      parameters:
        - name: store_id
          in: path
          required: true
          schema:
            type: string
          example: st_4bR8kM2wQ7xT1nV5cZ9pLdYe
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStoreOwnerRequest'
            example:
              name: Rosa Delgado
              email: rosa@tacoslamera.mx
        required: true
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OwnerInvitation'
              example:
                email: rosa@tacoslamera.mx
                invited_at: '2026-08-01T22:17:10Z'
        '401':
          description: >-
            The API key is missing, revoked, or not valid for this mode
            (`invalid_api_key`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: invalid_api_key
                  message: The API key is missing, revoked, or not valid for this mode.
                  param: null
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/invalid_api_key
                  request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
        '403':
          description: >-
            The key is valid and is read-only (`read_only_key`), so it can read
            but not create, change or cancel anything. Use a key issued without
            the read-only setting.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                read_only_key:
                  value:
                    error:
                      code: read_only_key
                      message: >-
                        This API key is read-only. Use a key without the
                        read-only setting to create, change or cancel anything.
                      param: null
                      retryable: false
                      doc_url: https://docs.listo.delivery/en/errors/read_only_key
                      request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
        '404':
          description: >-
            No object with that id exists for this key (`not_found`). Another
            account's objects are indistinguishable from ones that never
            existed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: not_found
                  message: No resource with that id exists for this API key.
                  param: null
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/not_found
                  request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
        '422':
          description: >-
            The request was understood and refused. `error.code` says why and
            `error.param` names the offending field when one applies.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: validation_failed
                  message: >-
                    This store already has an owner (rosita@tacoslamera.mx). A
                    store has one console login, and this endpoint cannot change
                    who holds it.
                  param: email
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/validation_failed
                  request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
      security:
        - BearerAuth: []
components:
  schemas:
    CreateStoreOwnerRequest:
      type: object
      description: The person to invite as the store's owner.
      properties:
        name:
          type: string
        email:
          type: string
      required:
        - name
        - email
    OwnerInvitation:
      type: object
      description: The invitation sent to a store's owner.
      properties:
        email:
          type: string
          description: The address the invitation went to.
        invited_at:
          type: string
          format: date-time
      required:
        - email
        - invited_at
    ErrorResponse:
      type: object
      description: The body of every 4xx and 5xx answer on this API.
      properties:
        error:
          $ref: '#/components/schemas/Error'
      required:
        - error
    Error:
      type: object
      description: |
        What every refusal and every failure looks like. `code` is the only
        field to branch on; `message` is for a human reading a log and may
        change wording or language without notice.
      properties:
        code:
          type: string
          description: |
            A stable, machine-readable name for what went wrong, such as
            `validation_failed`, `not_found` or `insufficient_balance`. Branch
            on this and never on `message`. The set grows additively: handle a
            code you do not know as the generic failure its status code says it
            is. Every code has a page under `doc_url`.
          example: validation_failed
        message:
          type: string
          description: |
            A sentence for the person debugging. Free to change wording or
            language, so never match on it.
          example: order_value_cents is required.
        param:
          type:
            - string
            - 'null'
          description: |
            The request field the refusal is about, when one applies, spelled
            as you sent it (`dropoff.street`, `manifest.items`). Null when the
            failure is not about one field.
          example: order_value_cents
        retryable:
          type: boolean
          description: |
            Whether sending the identical request again could succeed. `false`
            means it never will without a change on your side or in the world.
          example: false
        doc_url:
          type: string
          format: uri
          description: The documentation page for this `code`.
          example: https://docs.listo.delivery/en/errors/validation_failed
        request_id:
          type: string
          description: |
            Our id for this request, the same value as the `X-Request-Id`
            response header. Quote it to support, and filter
            `GET /v1/request_logs` by it.
          example: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
      required:
        - code
        - message
        - param
        - retryable
        - doc_url
        - request_id
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your API key, sent as `Authorization: Bearer <key>`. Live and test keys
        are separate; the key you send decides which world the call acts in. To
        try calls, use a test key (it starts with `lst_test_`): no real courier
        is sent and no real money moves, but webhooks fire and the
        `dropoff.phone` you give can receive WhatsApp messages, so use your own
        number. A live key (it starts with `lst_live_`) books and charges real
        deliveries.

````

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