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

# Undo a store's plan cancellation

> Undoes a cancellation before it takes effect. The plan goes back to
`active` and renews on its usual day, and nothing is charged now. A
plan that is not being canceled answers `200` with the store as it is,
so the call is safe to repeat.

`409 plan_ended` once the cancellation has taken effect: restart the
plan with `POST /v1/stores/{store_id}/plan` instead. `409
not_cancellable` while the plan is still paid by a restaurant that
connected to you, because its cancellation is not yours to undo.




## OpenAPI

````yaml /openapi/en.yaml delete /v1/stores/{store_id}/plan_cancellation
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}/plan_cancellation:
    delete:
      tags:
        - Stores
      summary: Undo a store's plan cancellation
      description: |
        Undoes a cancellation before it takes effect. The plan goes back to
        `active` and renews on its usual day, and nothing is charged now. A
        plan that is not being canceled answers `200` with the store as it is,
        so the call is safe to repeat.

        `409 plan_ended` once the cancellation has taken effect: restart the
        plan with `POST /v1/stores/{store_id}/plan` instead. `409
        not_cancellable` while the plan is still paid by a restaurant that
        connected to you, because its cancellation is not yours to undo.
      operationId: undoStorePlanCancellation
      parameters:
        - name: store_id
          in: path
          required: true
          schema:
            type: string
          example: st_4bR8kM2wQ7xT1nV5cZ9pLdYe
      responses:
        '200':
          description: The request succeeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Store'
              example:
                id: st_4bR8kM2wQ7xT1nV5cZ9pLdYe
                external_id: null
                name: Tortería La Mera Mera
                phone: '+528112345678'
                street: Av. Juan Palomar y Arias 765
                apt_unit: null
                lat: 20.691145
                lng: -103.426885
                pickup_notes: null
                timezone: America/Mexico_City
                default_business_fee_share: 0
                default_fee_split_mode: percent
                default_fee_split_amount_cents: null
                access_tier: api_only
                markup_cents: 0
                billing: partner
                plan: null
                test: false
                created_at: '2026-09-07T18:00:00Z'
        '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
        '409':
          description: >-
            The request conflicts with the state of the world. The object is no
            longer in a state that allows this (`not_cancellable`,
            `not_reschedulable`, `not_retriable`, `not_editable`,
            `not_refundable`, `not_reactivatable`, `plan_ended`), the
            cancellation did not go through (`cancel_failed`), the plan's charge
            is still with the bank (`charge_in_progress`), the idempotency key
            was reused with a different body (`idempotency_conflict`, which no
            retry fixes), the idempotency key is still held by the request that
            first sent it (`idempotency_conflict`, and once that request has
            answered the same call with the same key replays its answer), there
            is no webhook URL configured for the key's mode so there is nowhere
            to send (`webhook_endpoint_not_configured`), the event is a courier
            position, which is never sent again (`event_not_resendable`), the
            email already has a Listo account (`account_exists`), the store's
            pickup point is still being moved (`pickup_point_pending`), or no
            courier is free for this trip right now (`no_couriers_available`).
            Worth repeating unchanged after a few minutes: `cancel_failed`,
            `charge_in_progress`, `pickup_point_pending`,
            `no_couriers_available`, and a call refused because its idempotency
            key is still held (repeat it with the same key). Every other refusal
            here needs the request or the object to change first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: plan_ended
                  message: >-
                    This store's plan cancellation already took effect on
                    2026-07-06. Reactivate the plan with POST
                    /v1/stores/{id}/plan.
                  param: null
                  retryable: false
                  doc_url: https://docs.listo.delivery/en/errors/plan_ended
                  request_id: 0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10
      security:
        - BearerAuth: []
components:
  schemas:
    Store:
      type: object
      description: |
        A pickup location. Card readiness is deliberately NOT a field here: it
        is its own endpoint, with its own outstanding-requirements detail.
      properties:
        id:
          type: string
          description: The store id, `st_` prefixed.
          example: st_4bR8kM2wQ7xT1nV5cZ9pLdYe
        external_id:
          type:
            - string
            - 'null'
          description: |
            Your own reference for this location, if you sent one. Unique per
            key world, and filterable on the list, so you can reconcile against
            your own store book without storing our ids.
          example: pos-store-41
        name:
          type: string
          description: >
            The name you gave the store; for a restaurant that connected an

            account with several branches, the business and the branch, the way

            its own customers read it (`Tortería La Mera Mera, Zapopan`). A

            branch whose own name already begins with the business's name, such

            as `Tortería La Mera Mera Centro`, is published as it is. When

            you send a name, a value that begins with the store's business name

            and a comma is read as that business plus the branch, so sending
            back

            the name you read changes nothing.
        phone:
          type: string
          description: E.164, normalized on the way in.
        street:
          type: string
        apt_unit:
          type:
            - string
            - 'null'
        lat:
          type:
            - number
            - 'null'
          format: double
        lng:
          type:
            - number
            - 'null'
          format: double
        pickup_notes:
          type:
            - string
            - 'null'
          description: Standing instructions for the courier collecting here.
        timezone:
          type: string
          description: |
            The IANA zone the store keeps its hours in, normalized to one
            spelling per zone. Derived from the store's coordinates, and
            re-derived whenever the store's position moves. A zone you send
            yourself is used only when the position cannot answer (a store with
            no coordinates, or a position no zone claims), and one that
            disagrees with the position is neither stored nor treated as an
            error.
          example: America/Mexico_City
        default_business_fee_share:
          type: integer
          minimum: 0
          maximum: 100
          description: |
            The share of the delivery fee this store covers by default, as a
            percentage. The rest is charged to the customer. In force only when
            `default_fee_split_mode` is `percent`; in either amount mode this is
            the percentage the store keeps for when it goes back to one.
        default_fee_split_mode:
          type: string
          enum:
            - percent
            - business_amount
            - customer_amount
          description: |
            Which shape this store's default split has, and therefore which of
            the two fields above and below is in force. `percent` splits the fee
            by `default_business_fee_share`; `business_amount` means the store
            covers up to `default_fee_split_amount_cents` of it and the customer
            pays the rest; `customer_amount` is the mirror, the customer paying
            up to that amount and the store covering the rest.

            Read-only. It is set by the restaurant in its own Listo console, and
            sending `default_business_fee_share` on a create or an update moves
            the store back to `percent`.
        default_fee_split_amount_cents:
          type:
            - integer
            - 'null'
          minimum: 0
          description: |
            The amount the two amount modes divide the fee by, in cents. Null in
            `percent` mode, and never null in either amount mode. Read-only.
        access_tier:
          type: string
          enum:
            - api_only
            - wallet
            - full
          description: |
            How much of Listo the store itself uses. An `api_only` store's
            deliveries spend your balance, and a `wallet` or `full` store's
            spend its own; `billing` says which. Writable on create and update,
            and always `api_only` on a test store. A live store of an account
            whose restaurants pay their own Plan Listo cannot be `api_only`: it
            has no console to buy a plan from, so a create or an update that
            asks for it is refused with `422 validation_failed` and
            `param: "access_tier"`.
        markup_cents:
          type: integer
          minimum: 0
          description: |
            Your own markup on every delivery of this store, in cents. It is
            added to the delivery fee, split with the customer like the rest of
            the fee, and credited to your balance when the delivery is
            delivered. Writable on create and update.
        billing:
          type: string
          enum:
            - partner
            - store
          description: |
            Whose balance pays this store's deliveries, read off `access_tier`
            so you never have to derive it: `partner` is yours, `store` is the
            store's own. Read-only. It is not who pays the store's plan, which
            is `plan.paid_by`.
        test:
          type: boolean
        created_at:
          type: string
          format: date-time
        plan:
          description: >
            The store's Plan Listo, or `null` when the store needs no plan: a

            test store, or a live store created before Plan Listo existed, which

            keeps requesting couriers without one. It is also `null` when the

            store pays its own plan (every store of an account whose stores pay

            their own Plan Listo) and the restaurant has not bought one yet: the

            restaurant buys it in its own console, and there is nothing for you

            to pay or to reactivate. It is also `null` for a store of a partner

            on pricing with no Plan Listo, which needs no plan. When it is an

            object every

            key is present, and the ones that do not apply are `null`. See

            [Plan and
            billing](https://docs.listo.delivery/en/guides/plan-and-billing).
          type:
            - object
            - 'null'
          properties:
            status:
              type: string
              enum:
                - active
                - past_due
                - canceling
                - canceled
                - awaiting_payment
              description: >
                `active` is paid up and renews on `next_charge_on` (null on a

                plan that is passing to the restaurant, see `pending_paid_by`).
                `past_due`

                has an unpaid month, and its deliveries are refused with `402

                plan_required` until the charge goes through. `awaiting_payment`

                is a store whose first month your balance and card could not

                cover: it holds no plan yet, `price_cents` is the price it

                will be charged before IVA (the amount needed to pay is that

                plus IVA), every date is null, its deliveries are refused with

                `402 plan_required`, and Listo charges it by itself as soon as

                your balance or card covers it. `canceling` was

                canceled and is served up to the day before `ends_on`.

                `canceled` has ended, and can be restarted.
            price_cents:
              type: integer
              description: |
                What one month of this store's plan costs you, before IVA: your
                price, even while a restaurant that connected to you is still
                paying its own plan. When `paid_by` is `store` because the
                store pays its own plan, it is the price of one `interval` of
                the restaurant's own plan, which is not yours to pay.
              example: 199000
            currency:
              type: string
              example: MXN
            interval:
              type: string
              description: |
                How long one period of this plan is, and the cycle `price_cents`
                is the price of. `month` for every plan you pay. A store that
                pays its own plan reports the interval its restaurant bought,
                so `year` is a restaurant that paid twelve months at once.
              example: month
            current_period_ends_on:
              type:
                - string
                - 'null'
              format: date
              description: |
                The last day of the current period, already paid unless
                `status` is `past_due`.
            next_charge_on:
              type:
                - string
                - 'null'
              format: date
              description: |
                The day you are charged next: the next renewal, or, while a
                restaurant that connected to you still pays its own plan, your
                first charge. Null once the plan is ending or has ended, and
                null while it is `past_due`: the month owed is charged the
                moment your balance or saved card allows, and retried every
                day until it is paid. Null while `courtesy` is true, too. Null
                as well on a plan you pay that is passing to the restaurant
                (`pending_paid_by` is `store`): you pay the period you are in
                and nothing after it. When `paid_by` is `store` because the
                store pays its own plan, it is the day the restaurant's own plan
                renews, not a charge to you.
            canceled_at:
              type:
                - string
                - 'null'
              format: date-time
              description: When you canceled the plan.
            ends_on:
              type:
                - string
                - 'null'
              format: date
              description: >
                The day the plan stops serving the store, which is the day after

                the last day paid, or the day a plan whose charge was pending
                was

                canceled. Null unless the plan is ending or has ended.
            paid_by:
              type: string
              enum:
                - partner
                - store
              description: |
                Who pays this plan: `partner`, which is you, or `store`, the
                restaurant itself. `store` is a restaurant that connected to you
                and still pays its own plan, and every store that pays its own
                Plan Listo because your account is set up that way; none of
                those is yours to pay.
            pending_paid_by:
              type:
                - string
                - 'null'
              description: >
                Who will pay it from `pending_paid_by_on`, when that is about to

                change, or null. `partner` is you: a restaurant that connected
                to

                you and still pays its own plan, until the renewal you pay from.

                `store` is the restaurant: a plan you pay that passes to the

                restaurant at its next renewal because your account is set up so

                that each restaurant pays its own plan. You pay the period you

                are in and nothing after it, so `next_charge_on` is null.
            pending_paid_by_on:
              type:
                - string
                - 'null'
              format: date
              description: |
                The date that change lands (the day the new payer's first
                period starts), or null.
            courtesy:
              type: boolean
              description: |
                `courtesy` is true while Listo waives this store's plan: nothing
                is charged and `next_charge_on` is null.
          required:
            - status
            - price_cents
            - currency
            - interval
            - current_period_ends_on
            - next_charge_on
            - canceled_at
            - ends_on
            - paid_by
            - pending_paid_by
            - pending_paid_by_on
            - courtesy
      required:
        - id
        - external_id
        - name
        - phone
        - street
        - apt_unit
        - lat
        - lng
        - pickup_notes
        - timezone
        - default_business_fee_share
        - default_fee_split_mode
        - default_fee_split_amount_cents
        - access_tier
        - markup_cents
        - billing
        - test
        - created_at
        - plan
    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.