> ## 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.

# Request a withdrawal

> Requests a transfer of `amount_cents` out of your balance to your
registered bank account. The money is reserved the moment the request is
accepted, so a second request cannot spend it twice.

The request is settled by a human, usually within a business day, and
the outcome arrives as a `withdrawal.updated` webhook rather than in
this response. Asking for more than `available_cents` is a `402`.

You need a bank account registered before your first withdrawal, from
the [dashboard](https://partners.listo.delivery) rather than from this
API. Without one this call is `422 payout_account_not_ready` and nothing
is reserved. The same refusal covers the day after you CHANGE the
account: a moved destination cannot receive money for 24 hours, and the
`message` names the instant it can again.

### Same-day withdrawals

`kind` is optional and `standard` when you leave it out, which is the
free path above and is unchanged. Send `"kind": "same_day"` to have it
sent TODAY instead:

- It costs a flat fee, IVA included, which the response reports as
  `fee_cents`. The fee is a SECOND debit and never a deduction, so your
  bank still receives the whole `amount_cents` and your balance loses
  the two together.
- It can draw on cash that is still on hold, which the free path cannot,
  so `available_cents` is not the bound here.
- Requests are accepted around the clock. One made between 8 am and
  8 pm Mexico City is sent within 3 hours; one made outside those hours
  goes out the next morning. `due_at` on the response is the deadline
  your request actually carries, stamped when you made it.
- There is a minimum per request and a daily maximum per account.

Three refusals are specific to it and each names its bound in `message`:
`402 insufficient_balance` when the amount plus its fee is more than can
be withdrawn today, `422 same_day_cap_exceeded` when the day's maximum
has no room for it, and `422 same_day_below_minimum` when the amount is
under the floor. A `kind` that is neither word is
`422 validation_failed` with `param: "kind"`, never a quiet fallback to
the free path.




## OpenAPI

````yaml /openapi/en.yaml post /v1/withdrawals
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/withdrawals:
    post:
      tags:
        - Withdrawals
      summary: Request a withdrawal
      description: |
        Requests a transfer of `amount_cents` out of your balance to your
        registered bank account. The money is reserved the moment the request is
        accepted, so a second request cannot spend it twice.

        The request is settled by a human, usually within a business day, and
        the outcome arrives as a `withdrawal.updated` webhook rather than in
        this response. Asking for more than `available_cents` is a `402`.

        You need a bank account registered before your first withdrawal, from
        the [dashboard](https://partners.listo.delivery) rather than from this
        API. Without one this call is `422 payout_account_not_ready` and nothing
        is reserved. The same refusal covers the day after you CHANGE the
        account: a moved destination cannot receive money for 24 hours, and the
        `message` names the instant it can again.

        ### Same-day withdrawals

        `kind` is optional and `standard` when you leave it out, which is the
        free path above and is unchanged. Send `"kind": "same_day"` to have it
        sent TODAY instead:

        - It costs a flat fee, IVA included, which the response reports as
          `fee_cents`. The fee is a SECOND debit and never a deduction, so your
          bank still receives the whole `amount_cents` and your balance loses
          the two together.
        - It can draw on cash that is still on hold, which the free path cannot,
          so `available_cents` is not the bound here.
        - Requests are accepted around the clock. One made between 8 am and
          8 pm Mexico City is sent within 3 hours; one made outside those hours
          goes out the next morning. `due_at` on the response is the deadline
          your request actually carries, stamped when you made it.
        - There is a minimum per request and a daily maximum per account.

        Three refusals are specific to it and each names its bound in `message`:
        `402 insufficient_balance` when the amount plus its fee is more than can
        be withdrawn today, `422 same_day_cap_exceeded` when the day's maximum
        has no room for it, and `422 same_day_below_minimum` when the amount is
        under the floor. A `kind` that is neither word is
        `422 validation_failed` with `param: "kind"`, never a quiet fallback to
        the free path.
      operationId: createWithdrawal
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWithdrawalRequest'
            example:
              amount_cents: 250000
              kind: standard
        required: true
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Withdrawal'
              example:
                id: wd_2xN6bK9mR4tQ7vC1sZ8pLwYe
                amount_cents: 250000
                currency: MXN
                status: pending
                requested_at: '2026-07-30T18:30:00Z'
                resolved_at: null
                test: false
                clave_de_rastreo: null
                fee_cents: 0
                kind: standard
                due_at: null
        '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
        '402':
          description: >-
            The prepaid balance does not cover this request
            (`insufficient_balance`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: insufficient_balance
                  message: The account balance does not cover this request.
                  param: null
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/insufficient_balance
                  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
        '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: Amount cents must be an integer
                  param: amount_cents
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/validation_failed
                  request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
      security:
        - BearerAuth: []
components:
  schemas:
    CreateWithdrawalRequest:
      type: object
      description: How much to move out of your balance.
      properties:
        amount_cents:
          type: integer
          minimum: 1
          description: What your bank receives, in centavos.
        kind:
          type:
            - string
            - 'null'
          enum:
            - standard
            - same_day
            - null
          description: Defaults to `standard`.
      required:
        - amount_cents
    Withdrawal:
      type: object
      description: A request to move money out of your balance to your bank account.
      properties:
        id:
          type: string
          description: The withdrawal id, `wd_` prefixed.
          example: wd_2xN6bK9mR4tQ7vC1sZ8pLwYe
        amount_cents:
          type: integer
          description: |
            What your bank receives. On a `same_day` withdrawal the fee is a
            SECOND debit rather than a deduction, so this figure is what lands
            either way and `fee_cents` is what your balance loses on top of it.
        fee_cents:
          type: integer
          description: |
            What the withdrawal cost, IVA included. Always `0` on a `standard`
            withdrawal. Always present.
        currency:
          type: string
          enum:
            - MXN
        kind:
          type: string
          enum:
            - standard
            - same_day
          description: |
            `standard` is the free path: your released balance only, sent in 1
            to 3 business days. `same_day` costs `fee_cents`, can reach cash
            still on hold, and is sent the same day within the hours in
            `POST /v1/withdrawals`.
        status:
          type: string
          enum:
            - pending
            - paid
            - failed
          description: |
            `pending` until a human settles it. The move to `paid` or `failed`
            is what `withdrawal.updated` announces.
        requested_at:
          type:
            - string
            - 'null'
          format: date-time
        due_at:
          type:
            - string
            - 'null'
          format: date-time
          description: |
            When a `same_day` withdrawal is due to have been sent, stamped when
            you requested it and never recalculated afterwards. Null on every
            `standard` withdrawal: the free path promises a range of business
            days rather than an instant.
        resolved_at:
          type:
            - string
            - 'null'
          format: date-time
          description: Null while still pending.
        clave_de_rastreo:
          type:
            - string
            - 'null'
          nullable: true
          description: |
            The SPEI tracking key for the transfer: the identifier your own bank
            uses to locate the deposit. This is what you reconcile a payout
            against a bank statement with, and it is the same key you see in the
            dashboard for this withdrawal.

            Always present, null until the transfer is sent. It stays null
            forever on a `failed` withdrawal, because no transfer happened.
          example: MBAN01002608120001234567
        test:
          type: boolean
      required:
        - id
        - amount_cents
        - fee_cents
        - currency
        - kind
        - status
        - requested_at
        - due_at
        - resolved_at
        - clave_de_rastreo
        - test
    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.