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

> ## Agent Instructions
> Flow Messaging is one HTTP API (https://api.flow.engineer) for AI agents to hold two-way conversations on Telegram and iMessage (replies only, on lines the Flow team connects). WhatsApp is coming and not available yet.
> Build the app on the REST API. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript SDK, which wraps it) for everything it does at runtime. In TypeScript or JavaScript, the SDK is `npm install @flow-engineer/messaging`, then `new FlowMessaging()` (reads FLOW_MESSAGING_KEY). Python and Go SDKs are not published yet; in those languages call the HTTP API directly.
> No key yet? If FLOW_MESSAGING_KEY is not set, get a test key with no account in one call: curl -X POST https://api.flow.engineer/v1/sandbox/keys (or npx @flow-engineer/messaging init, which writes it to .env). Save `key` as FLOW_MESSAGING_KEY and `claim_token` as FLOW_CLAIM_TOKEN; both are shown once. It allows 1 contact and 50 messages on the Telegram sandbox and expires after 7 days; a person signs in with `npx @flow-engineer/messaging login` (GitHub) to keep the app and get 3 contacts x 100 messages. Signed in, live keys (fk_live_...) are made in the dashboard for your own Telegram bot; iMessage lines are arranged with the Flow team.
> The hosted MCP server (https://api.flow.engineer/mcp) is optional, for testing during development. Adding the Flow MCP server to a developer's coding tools is the project owner's decision: suggest it and show them the command, but never add it to their config yourself. The command for Claude Code: claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY". If its tools are already available to you, use them for sandbox testing.
> Authenticate with `Authorization: Bearer fk_test_...` (sandbox) or `fk_live_...` (real contacts). Never put a live key in client-side code.
> Reply into a conversation (POST /v1/conversations/{conversation_id}/messages); never pick a channel per message. Switch on `error.type`, not the message text.
> Full documentation for agents: https://docs.flow.engineer/llms-full.txt. OpenAPI spec: https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml.

# Delete a template

> Deletes the template at Meta and here. Messages already sent with it are unaffected.



## OpenAPI

````yaml /openapi.yaml delete /v1/templates/{template_id}
openapi: 3.1.0
info:
  title: Flow Messaging API
  version: '2026-11-01'
  summary: Two-way messaging for AI agents on WhatsApp, Telegram and iMessage.
  description: >
    Flow Messaging lets an AI agent hold conversations with people on
    **WhatsApp**,

    **Telegram** and **iMessage** through one HTTP API. Flow hosts the senders
    (bots,

    numbers and lines), receives every inbound message, and passes every send
    through

    one gate that enforces each channel's rules. You never handle vendor keys or

    channel webhooks.


    Channels today: Telegram is live. iMessage is live for replies only, on
    lines the

    Flow team connects to your app. WhatsApp is not available yet (it waits on
    Meta's

    approval): requesting a WhatsApp sender answers `501 not_implemented` until
    it

    ships, and the WhatsApp-only parts (templates, the 24-hour window) apply
    then.


    This API is in **beta**: the shape may still change before it is declared
    stable.

    Changes are released as new dated versions (see Versioning).


    ## Concepts


    - An **app** is one agent integration. It owns API keys, webhook endpoints
    and
      settings, and has a test mode and a live mode.
    - A **sender** is what your agent talks *from*: a Telegram bot, a WhatsApp
    number
      or an iMessage line. Shared sandbox senders serve many apps; dedicated senders
      are yours alone.
    - A **contact** is a person on one channel. There is no cross-channel
    identity: a
      contact on Telegram and a contact on WhatsApp are different contacts, even if
      they are the same person. Never assume a contact has a phone number.
    - A **conversation** is one sender talking with one contact. You reply into
    a
      conversation; you never pick a channel per message.
    - A **message** carries one piece of typed **content** (text, media, voice,
      buttons, a reaction, a template, ...). The same content union is used in both
      directions.
    - An **event** is one entry in your app's ordered log. Everything you
    receive
      (inbound messages, delivery statuses, reactions, sender changes) is an event,
      delivered by webhook, by the live stream (`GET /v1/stream`) and by polling
      (`GET /v1/events`).

    ## Authentication


    Every request carries an API key as a bearer token:


    ```

    Authorization: Bearer fk_live_...

    ```


    Keys belong to one app and one mode. `fk_test_` keys see only sandbox
    senders and

    test data, and nothing they send reaches a real contact unless that contact
    joined

    the sandbox. `fk_live_` keys reach real contacts through your dedicated
    senders.

    A key is shown once when it is created and stored only as a hash. Keys are
    never

    accepted in the query string. The live stream (`GET /v1/stream`) also takes
    the

    key as a WebSocket subprotocol, for browsers, which cannot set headers on a

    WebSocket (see that endpoint).


    ### No key yet? Get one in one call


    Anyone, including an AI coding agent, can get a test key without signing up:


    ```

    curl -X POST https://api.flow.engineer/v1/sandbox/keys

    ```


    The answer (`SandboxKey`) holds a `fk_test_` key for a new app of its own,
    shown

    once, and the sandbox senders with the links and join code a person uses to

    join it. Save the key (for example as `FLOW_MESSAGING_KEY`) and the

    `claim_token`. This key needs no account and has a **sandbox allowance**: 1

    contact and 50 messages sent in total on the Telegram sandbox (and
    WhatsApp's

    when it opens), and it **expires after 7 days**. Only messages your agent
    sends count; inbound

    messages are free. iMessage is not part of the allowance.


    To keep the app, a person signs in with GitHub through the device

    flow: the agent or the CLI (`npx @flow-engineer/messaging login`) calls
    `POST

    /v1/device/authorizations` with the `claim_token`, shows the person the

    `verification_uri` and `user_code` (they type the code on that page), and
    polls `POST /v1/device/token`

    until it returns a key. The app is claimed: its data and keys are kept, its

    keys no longer expire, and it moves under the person's signed-in allowance
    of

    3 contacts and 100 messages each, **one allowance per person, shared by
    every

    app they own or claim** (a person may claim up to 10 apps). `GET /v1/app`
    shows what is left of the allowance

    (`allowance`). When it runs out, sends answer `403 permission` with

    `channel_code` `sandbox_allowance_used`.


    ## Versioning


    The API is versioned by date. Send `Flow-Version: 2026-11-01` to choose a
    version;

    without the header, the version pinned to your app when it was created is
    used.

    Responses echo the version that served them in `Flow-Version`.


    ## Idempotency


    Every `POST` accepts an `Idempotency-Key` header (any unique string up to
    255

    characters; a UUID or ULID is a good choice). Keys are kept for 24 hours per
    app

    and mode. Repeating a request with the same key returns the first answer,
    with

    the header `Idempotent-Replayed: true`, and does not act twice. Otherwise a
    key

    already used answers `409 idempotency_conflict`, and its `channel_code` says

    which case it is:


    - `body_mismatch`: the key was used with a different method, path or body.
    Use a
      new key for a new request; do not retry.
    - `in_progress`: the first request with this key is still running. Wait
      `retry_after` seconds and repeat the identical request with the same key to
      get its answer.
    - `secret_not_kept`: the first request went through, but its answer carried
    a
      secret shown only once (creating a webhook endpoint, rotating its secret), so
      that answer was not kept.

    Answers that are worth retrying (`429`, `500`, `502`, `503`, `504`) are not

    kept, so a retry with the same key runs again.


    ## Errors


    Errors share one shape:


    ```json

    {"error": {
      "type": "outside_window",
      "message": "Last message from the contact was 31h ago; WhatsApp allows only templates now.",
      "hint": "Send a template instead: POST /v1/messages with content.type=template.",
      "doc_url": "https://api.flow.engineer/docs/errors/outside_window",
      "conversation": "conv_01JB..."}}
    ```


    `error.type` is a closed list (see the `ErrorType` schema); switch on it,
    not on

    the message text. Every error carries `hint`, one sentence saying what to
    change

    for this case, and `doc_url`, the page for its type

    (`https://api.flow.engineer/docs/errors/<type>`: what it means, why it
    happens, how

    to fix it, with code). Errors that can be retried carry `retry_after` in
    seconds.

    Coding agents can act on `hint` directly; the MCP tool `explain_error`
    returns

    the page for a type.


    ## Pagination


    Lists are paged with cursors. Pass `limit` (1 to 100, default 20) and either

    `after` or `before`, set to the ID of an item you already have. IDs are
    prefixed

    ULIDs, so they sort by creation time. The event log (`GET /v1/events`) is
    oldest

    first, so `after` moves forward in time; every other list is newest first,
    so

    `after` moves back in time. A list answer says `has_more` when more items
    lie in

    that direction.


    ## Rate limits


    Requests are limited per key. Every answer carries `RateLimit-Limit`,

    `RateLimit-Remaining` and `RateLimit-Reset`. Sends are also paced per sender
    by

    the send gate (see below): each sender has a sending rate it may not exceed.
    Going

    over either limit answers `429` with type `rate_limited`, a `Retry-After`

    header and `error.retry_after` in seconds; wait that long and retry with the

    same `Idempotency-Key`. The send gate also answers `429` with type

    `new_contact_limit` when a sender has used its budget for starting

    conversations.


    ## The send gate


    Every send (HTTP, the stream, or a reply in a webhook response) passes the
    same

    gate:


    - **WhatsApp**: free-form messages only while the contact's last message is
    less
      than 24 hours old (the window); otherwise only a `template`
      (`409 outside_window`).
    - **iMessage**: a line may only reply unless it can start conversations; a
    new
      conversation from a reply-only line is refused (`403 permission`). A line that
      can start conversations spends the sender's new-contact budget, and a contact
      who has never messaged the line (or opted in) gets a `message.failed` event
      with `outside_window`.
    - **Telegram**: the contact must have started the bot; Telegram enforces
    this.

    - **Sandbox senders**: only contacts who joined through your app (by sending
    its
      join code, for example `join brave-otter-40718263`).

    Starting a conversation spends budget per sender (new contacts per day and
    per

    hour, with a warm-up curve for new lines) and per plan. When abuse signals
    trip

    (the same text to many new contacts, many starts with no reply, blocks, a
    drop

    in WhatsApp quality) the sender is throttled and you receive

    `sender.status_changed`: until the sender's `throttled_until`, starts fail
    with

    `429 sender_throttled`, while replies into existing conversations still go.
    The

    sender recovers by itself, with another `sender.status_changed`.


    ## Content a channel cannot show


    Nothing is converted silently. When a channel cannot show some content, the
    send

    fails with `422 unsupported_content`, unless the request set `fallback`:
    then

    the fallback is sent and the message reports what was actually shown in

    `delivered_as`. `GET /v1/capabilities` says what a conversation's channel

    supports right now.


    ## Webhooks


    Register endpoints with `POST /v1/webhook_endpoints`. Each event is `POST`ed
    as

    JSON (the `event` webhook below), in order per conversation, and signed:


    ```

    Flow-Signature:
    t=1791763200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

    ```


    `v1` is the lowercase hex HMAC-SHA256, keyed with the endpoint's signing
    secret

    (`whsec_...`), of the string `t.{t}.{body}`: the letter `t`, a dot, the

    timestamp from the header, a dot, and the raw request body exactly as
    received.

    Compare in constant time and reject timestamps more than 5 minutes from your

    clock. While a secret is being rotated (`POST

    /v1/webhook_endpoints/{webhook_endpoint_id}/rotate_secret`), the header
    carries

    one `v1` per active secret; accept the request if any of them matches.


    Answer `2xx` within 10 seconds. To reply at once, answer a
    `message.received`

    event with `200` and a body `{"reply": <content>}` or `{"reply": [<content>,
    ...]}`

    (up to 10 pieces), optionally with `fallback` as on HTTP sends; the reply is
    sent

    into the event's conversation through the send gate, which saves a round
    trip.

    A string is text: `{"reply": "Hi"}` sends `{"type": "text", "text": "Hi"}`.

    Any other `2xx` answer (an empty body, plain text such as `OK`, or JSON
    without

    `reply`) sends nothing and is not an error. A JSON object whose `reply` is
    not

    valid `WebhookReply` content, or a body that starts with `{` but is not
    valid

    JSON, sends nothing at all and is recorded on the

    delivery as an `invalid_request` error (the MCP tool
    `get_webhook_deliveries`

    shows it); the delivery still counts as delivered and is not retried. Slow
    agents answer `200 {}` at once and send later with

    `POST /v1/conversations/{conversation_id}/messages`. Failed deliveries are
    retried

    with backoff for 3 days; within a conversation, later events wait behind a

    failing one. After that the event stays in the log, marked failed, and can
    be

    read again with `GET /v1/events`.


    Inbound messages keep the channel's order within a conversation. iMessage:

    inbound order is best-effort (arrival order); messages sent within about a
    second

    of each other may arrive out of order.


    ## MCP server


    `https://api.flow.engineer/mcp` is a Model Context Protocol server
    (Streamable

    HTTP transport, protocol version 2025-06-18 or later) for coding agents such
    as

    Claude Code, Codex and Cursor. It is not a REST endpoint: clients `POST`

    JSON-RPC messages to it (and `GET` answers `405`, since the server keeps no

    sessions). Authenticate with the same API key as the REST API:


    ```

    claude mcp add --transport http flow https://api.flow.engineer/mcp --header
    "Authorization: Bearer $FLOW_MESSAGING_KEY"

    ```


    Without a key, `/mcp` answers `401` and its `hint` gives the one call that
    gets

    a test key (`curl -X POST https://api.flow.engineer/v1/sandbox/keys`).


    The key's mode chooses the tools. A `fk_test_` key gets build-time tools to

    integrate and verify an app end to end: `whoami`, `sandbox_join`,

    `send_test_message`, `wait_for_event`, `list_events`,
    `get_webhook_deliveries`,

    `replay_event`, `capabilities` and `explain_error`. A `fk_live_` key gets

    production tools: `send_message`, `reply`, `react`, `typing`,

    `list_conversations` and `get_conversation_messages`, plus `whoami`,

    `capabilities` and `explain_error`. Every send passes the same send gate as

    the REST API, and a refusal comes back as the tool's error with the same

    `type`, `hint` and `doc_url`.
  contact:
    name: Flow Engineer
    url: https://docs.flow.engineer
  license:
    name: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
servers:
  - url: https://api.flow.engineer
    description: Production (asia-south1). Test and live mode are chosen by the API key.
security:
  - bearerAuth: []
tags:
  - name: Messages
    description: >-
      Send content into conversations, start conversations, edit and unsend
      messages.
  - name: Conversations
    description: >-
      One sender talking with one contact. Read history, show typing, mark as
      read.
  - name: Events
    description: >-
      The ordered log of everything that happened in your app. Catch up, replay
      and debug.
  - name: Stream
    description: Live events over a WebSocket, resumable from any event.
  - name: Capabilities
    description: >-
      What a conversation's channel can show right now, and whether its window
      is open.
  - name: Files
    description: >-
      Media in and out. Flow fetches channel media for you and serves it through
      short-lived URLs.
  - name: Senders
    description: >-
      The Telegram bots, WhatsApp numbers and iMessage lines your app sends
      from.
  - name: Templates
    description: WhatsApp message templates, mirrored from Meta with their approval status.
  - name: Webhook endpoints
    description: Where Flow delivers your app's events.
  - name: Contacts
    description: People your app talks with, one per channel.
  - name: App
    description: The app and key making the request.
  - name: Onboarding
    description: >
      Get a test key without an account (`POST /v1/sandbox/keys`), and sign in
      with

      GitHub through the device flow to claim the app and lift its

      sandbox allowance. These endpoints take no API key.
paths:
  /v1/templates/{template_id}:
    parameters:
      - $ref: '#/components/parameters/TemplateId'
      - $ref: '#/components/parameters/FlowVersion'
    delete:
      tags:
        - Templates
      summary: Delete a template
      description: >-
        Deletes the template at Meta and here. Messages already sent with it are
        unaffected.
      operationId: deleteTemplate
      responses:
        '200':
          description: The template was deleted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deleted'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        default:
          $ref: '#/components/responses/Error'
components:
  parameters:
    TemplateId:
      name: template_id
      in: path
      required: true
      description: The template's ID.
      schema:
        $ref: '#/components/schemas/TemplateId'
    FlowVersion:
      name: Flow-Version
      in: header
      required: false
      description: >-
        The API version to use, as a date. Without it, the version pinned to
        your app when it was created is used.
      schema:
        type: string
        format: date
        examples:
          - '2026-11-01'
  schemas:
    Deleted:
      type: object
      description: Confirms that an object was deleted.
      required:
        - id
        - deleted
      properties:
        id:
          type: string
          description: The deleted object's ID.
        deleted:
          type: boolean
          description: Always `true`.
    TemplateId:
      type: string
      description: A template ID, `tpl_` and a ULID.
      pattern: ^tpl_[0-9A-HJKMNP-TV-Z]{26}$
      examples:
        - tpl_01JB8Z9X2D4F6H8K0M2P4R6T8V
    Error:
      type: object
      description: The body of every error answer.
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
    ErrorBody:
      type: object
      description: The details of an error.
      required:
        - type
        - message
        - hint
        - doc_url
      properties:
        type:
          $ref: '#/components/schemas/ErrorType'
        message:
          type: string
          description: >-
            A sentence for a developer saying what went wrong. Do not parse it;
            switch on `type`.
        hint:
          type: string
          description: >
            One sentence saying what to change to make the request succeed,
            specific

            to this case, for example "Send a template instead: POST
            /v1/messages with

            content.type=template." Written for developers and coding agents
            alike. Do

            not parse it; it may be reworded at any time.
          examples:
            - >-
              Send a template instead: POST /v1/messages with
              content.type=template.
        doc_url:
          type: string
          format: uri
          description: >-
            The documentation page for this error's type,
            `https://api.flow.engineer/docs/errors/<type>`. It says what the
            error means, why it happens and how to fix it, with code.
          examples:
            - https://api.flow.engineer/docs/errors/outside_window
        param:
          type: string
          description: >-
            The request parameter the error is about, as a dotted path
            (`content.buttons`, `limit`).
        retry_after:
          type: integer
          minimum: 0
          description: >-
            Seconds to wait before retrying, for errors that clear by
            themselves.
        conversation:
          $ref: '#/components/schemas/ConversationId'
        sender:
          $ref: '#/components/schemas/SenderId'
        channel_code:
          type: string
          description: >
            For `channel_error`, the channel's own error code, as given by the

            channel. For some other errors, Flow's own code naming the case, for

            example `sandbox_allowance_used` (`permission`: the sandbox
            allowance is

            used up), `sandbox_contact_limit` (`permission`: the allowance has
            no

            room for another contact), `sandbox_channel_not_included`
            (`permission`:

            the allowance does not cover this sandbox channel),
            `sign_in_required`

            (`permission`: an app made without an account cannot do this) or

            `sandbox_key_expired` (`authentication`: a key from `POST

            /v1/sandbox/keys` passed its `expires_at`; `permission`, for a send

            that carries no key, such as a reply in a webhook answer, from such
            an

            app). For `idempotency_conflict`, always one of `body_mismatch` (the

            key was used for a different request), `in_progress` (the first

            request is still running; `retry_after` says when to repeat it) or

            `secret_not_kept` (its answer carried a secret shown only once).
        request_id:
          type: string
          description: Flow's ID for this request. Quote it when asking for help.
    ErrorType:
      type: string
      description: >
        What went wrong. A closed list: new types arrive only with a new API
        version.

        Each type has a page at `https://api.flow.engineer/docs/errors/<type>`,
        given in

        the error's `doc_url`; the error's `hint` says what to change for the
        case.


        - `invalid_request` (400): the request is malformed or a parameter is
        invalid. Fix the parameter named in `param`; the `hint` says what it
        must look like.

        - `authentication` (401): the API key is missing, malformed, unknown,
        revoked or expired. Send `Authorization: Bearer fk_test_...` or
        `fk_live_...` with a current key; with no key at all, get a test key
        with `POST /v1/sandbox/keys`. A sandbox key past its `expires_at` has
        `channel_code` `sandbox_key_expired`: sign in to claim the app, or get a
        new key.

        - `permission` (403): the key may not do this, for example a test key
        using a live sender, a sandbox contact who joined another app, a new
        conversation from an iMessage line that may only reply, or a send past
        the app's sandbox allowance (`channel_code` `sandbox_allowance_used`,
        `sandbox_contact_limit` or `sandbox_channel_not_included`). Use the key
        of the right mode, have the contact send your join code, wait for the
        contact to message the line first, or sign in through the device flow to
        lift the allowance.

        - `not_found` (404): no such object for this app and mode. Check the
        ID's prefix and that it was made with a key of the same mode (test and
        live data are separate).

        - `idempotency_conflict` (409): the idempotency key was used for a
        different request (`channel_code` `body_mismatch`: use a new key), or
        that request is still running (`in_progress`: wait `retry_after` seconds
        and repeat it with the same key), or it already created a secret that is
        shown only once (`secret_not_kept`: creating a webhook endpoint,
        rotating its secret; a secret that was lost must be rotated again).

        - `outside_window` (409, or in `message.failed`): the channel will not
        deliver outside its conversation window. On WhatsApp the 24-hour window
        is closed: send a `template` (`POST /v1/messages` with
        `content.type=template`), or wait for the contact to write. On iMessage
        the contact has not messaged the line (or opted in) yet, or not
        recently, so the failure arrives as a `message.failed` event: wait for
        the contact to write, then reply in that conversation. Typing and read
        receipts answer `409 outside_window` directly when the channel's window
        is closed (iMessage typing works only within 5 minutes of the contact's
        last message); ignore it and send your reply.

        - `unsupported_content` (422): the channel cannot show this content and
        no `fallback` was set. Set `fallback` (`"auto"` or your own content), or
        check `GET /v1/capabilities` first.

        - `new_contact_limit` (429): the sender has used its budget for starting
        conversations. Wait `retry_after` seconds; replies into existing
        conversations still go.

        - `sender_throttled` (429): abuse signals tripped (the same text to many
        new contacts, many starts with no reply, blocks), so the sender may not
        start conversations until `retry_after`; replies into existing
        conversations still go. Personalise first messages and start only
        conversations people expect.

        - `file_blocked` (422): the file failed the malware scan and was not
        stored. Send a different file.

        - `rate_limited` (429): either too many requests for this key (the
        per-key request limit, reported in the `RateLimit-*` headers) or sends
        faster than the sender's sending rate (pacing). Wait `Retry-After`
        (`retry_after`) seconds and retry with the same `Idempotency-Key`.

        - `channel_error` (502, or in `message.failed`): the channel refused or
        failed the message, or timed out; `channel_code` carries its own code.
        Read `message`, change what the channel objected to, and send again.
        From typing and read receipts, which call the channel at once, it is
        safe to ignore.

        - `not_implemented` (501): this endpoint or channel is not live yet
        during the beta. Use a channel that is live (Telegram, iMessage), or
        check the changelog.

        - `api_error` (500, 503): something went wrong on Flow's side. Retry
        with the same idempotency key after `retry_after` seconds, and quote
        `request_id` if it persists.
      enum:
        - invalid_request
        - authentication
        - permission
        - not_found
        - idempotency_conflict
        - outside_window
        - unsupported_content
        - new_contact_limit
        - sender_throttled
        - file_blocked
        - rate_limited
        - channel_error
        - not_implemented
        - api_error
    ConversationId:
      type: string
      description: A conversation ID, `conv_` and a ULID.
      pattern: ^conv_[0-9A-HJKMNP-TV-Z]{26}$
      examples:
        - conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1
    SenderId:
      type: string
      description: A sender ID, `snd_` and a ULID.
      pattern: ^snd_[0-9A-HJKMNP-TV-Z]{26}$
      examples:
        - snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T
  responses:
    Unauthenticated:
      description: >-
        The API key is missing, malformed, unknown, revoked or expired
        (`authentication`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: authentication
              message: No valid API key was given.
              hint: >-
                Send the header Authorization: Bearer fk_test_... (or
                fk_live_...); no key yet? Get a test key with curl -X POST
                https://api.flow.engineer/v1/sandbox/keys
              doc_url: https://api.flow.engineer/docs/errors/authentication
    NotFound:
      description: No such object for this app and mode (`not_found`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: >
        Too many requests (`rate_limited`): either this key's request limit (see
        the

        `RateLimit-*` headers) or the sender's sending rate (pacing) was
        exceeded;

        retry after `Retry-After` seconds (also `error.retry_after`) with the
        same

        `Idempotency-Key`. Or the sender has used its budget for starting

        conversations (`new_contact_limit`), or the sender is throttled after
        abuse

        signals (`sender_throttled`); wait `retry_after` seconds.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: new_contact_limit
              message: This sender has started its 15 new conversations for today.
              hint: >-
                Retry after 3600 seconds; replies into existing conversations
                still go.
              doc_url: https://api.flow.engineer/docs/errors/new_contact_limit
              retry_after: 3600
              sender: snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T
    Error:
      description: An error. `error.type` says which (see `ErrorType`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  headers:
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
    RateLimitLimit:
      description: Requests allowed in the current window for this key.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests left in the current window for this key.
      schema:
        type: integer
    RateLimitReset:
      description: Seconds until the current window resets.
      schema:
        type: integer
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: fk_test_... or fk_live_...
      description: >
        An API key of one app, sent as `Authorization: Bearer <key>`. Keys start
        with

        `fk_test_` (test mode: sandbox senders and test data only) or `fk_live_`

        (live mode). Keep live keys on your server; never ship them in an app or
        page.

````

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