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

# Post apiv2responses cancel

> `POST /api/v2/responses/{response_id}/cancel` (§30).

§30 offers this endpoint as optional and attaches one condition — *if
implemented in Phase 3, it must be safe and idempotent* — so both
properties are structural rather than checked:

**Safe.** `resolve_run` runs first and applies the same four bindings
every other read does, so a guessed id answers 404 whether it names
nothing or another tenant's run. A run that has already reached a terminal
status is left exactly as it is: the conditional `UPDATE` in
`runtime.cancellation.request_stop` names the states it is legal to stop
from, so a run that finished between the read and the write is not
reopened by a cancel that lost the race.

**Idempotent.** Stopping is `Event.set()` plus that `UPDATE`; doing either
twice is indistinguishable from doing it once. A second call answers the
same way as the first, and a call for a finished run answers 200 rather
than an error, because "it already stopped" is the outcome the caller
wanted.

Two statuses, and the difference is information rather than success:
**202** the run was in flight and will stop at its next checkpoint;
**200** there was nothing left to stop. Cancellation is cooperative — see
`runtime.cancellation` for what that promises at each point of a turn —
so 202 is the honest code for "accepted", not 204.

Any run this caller may read can be cancelled here, not only those from
`POST /v2/responses`. The path is §30's; the ids are one namespace.

**Required scope:** `responses:create`. A key without it is a 403 naming the scope; the endpoint is never silently skipped.



## OpenAPI

````yaml /openapi/v2.yaml post /api/v2/responses/{response_id}/cancel
openapi: 3.0.3
info:
  description: >
    The Impel Labs public API, version 2.


    Every operation here is reachable with a machine credential and demands a

    scope, named on the operation as `x-required-scope`. Scopes are not implied
    by

    one another: a key that may read conversations cannot run an agent, and a
    key

    that may edit an assistant cannot preview its drafts.


    ## Streaming


    Set `"stream": true` on any execution endpoint and the body is

    `text/event-stream` instead of JSON. One contract for all three

    endpoints — `/responses`, `/conversations/{id}/messages` and

    `/agents/{id}/runs` — so an event means the same thing wherever it arrives.


    Event types (contract `2026-09-07`):


    - `response.started`

    - `response.output_text.delta`

    - `response.output_json.delta`

    - `response.tool.started`

    - `response.tool.completed`

    - `response.tool.failed`

    - `response.completed`

    - `response.failed`

    - `response.cancelled`


    Exactly one terminal event is emitted per stream, ever:
    `response.cancelled`, `response.completed`, `response.failed`. A stream

    that ends without one is a bug on this side, not a state to handle.


    A line beginning `:` is a comment, sent about every

    15 seconds so that proxies do not close an idle

    connection. Comments are not events and must be skipped, not parsed.


    **Failures before the first event are not streamed.** No credential, no

    credits, an unpublished agent, a schema this runtime cannot enforce: each is

    the ordinary JSON error with its own status code, because nothing has been

    written to the wire yet and §36 says not to answer 200 for a failed
    execution.

    Once `response.started` is on the wire the response is already a 200, and a

    later failure arrives as `response.failed` carrying the same

    `{"error": {code, message, request_id}}` payload.


    A client that disconnects is not billed differently and does not leave a run

    running forever: the run is cancelled and settles once, like any other.


    ## Structured output


    Ask for structured output (§31). `json_schema` validates the model's output
    against `schema` and retries a mismatch once before failing with **422**
    `structured_output_failed`, which names the paths that did not match. The
    bound is read before the run starts, so the number of model calls a
    structured request can cost is knowable in advance.


    The schema is checked when the request is parsed — before authorization,
    before the idempotency claim, and before any spend — so a schema this
    runtime cannot enforce is a 400 rather than a charge. `strict` is accepted
    only as `true`: there is no mode in which a schema is requested and not
    enforced.


    `POST /api/v2/reports` accepts `format: "json"` with a `schema`.


    ## Preview and drafts


    Run the agent's unpublished configuration (§94). **Asking is free; being
    allowed is not.** A machine caller needs the `agent:preview` scope, which is
    issued on its own and is not implied by any other — a key that may edit an
    assistant does not thereby preview its drafts.


    Without permission the answer is **404, not 403**: confirming that a draft
    exists but may not be run is the confirmation the not-found rule exists to
    withhold.


    Statuses a preview run may execute: `active`, `draft`, `paused`, `preview`.
    Without preview, only `active`.


    ## Errors


    Every failure is `{"error": {"code", "message", "request_id"}}`. Switch on

    `code`; `message` is prose. A failure that ends a run that had already
    started

    also carries `id`, `status` and `trace_id` (see `RunError`).


    | code | HTTP | run status |

    | --- | --- | --- |

    | `agent_not_available` | 409 | `failed` |

    | `agent_not_found` | 404 | `failed` |

    | `authentication_error` | 401 | `failed` |

    | `conversation_not_found` | 404 | `failed` |

    | `idempotency_conflict` | 409 | `failed` |

    | `internal_error` | 500 | `failed` |

    | `invalid_context` | 403 | `failed` |

    | `invalid_request` | 400 | `failed` |

    | `knowledge_unavailable` | 503 | `failed` |

    | `not_implemented` | 501 | `failed` |

    | `permission_denied` | 403 | `failed` |

    | `provider_unavailable` | 502 | `failed` |

    | `rate_limited` | 429 | `blocked` |

    | `run_not_found` | 404 | `failed` |

    | `structured_output_failed` | 422 | `failed` |

    | `tool_execution_failed` | 502 | `failed` |

    | `usage_blocked` | 402 | `blocked` |


    Two of those are decisions rather than faults, and both end a run as
    `blocked`:

    `usage_blocked` (**402** — the workspace cannot spend) and `rate_limited`

    (**429**). Filing either as `failed` is what makes an error-rate dashboard

    useless and a support answer wrong.


    A resource in another workspace and a resource that does not exist answer

    **404** identically, byte for byte. There is no 403 that confirms existence.


    ## Paging


    Cursor-based, `?limit=&after=` where the endpoint says so. Cursors are
    opaque

    base64url — do not build one. A raw ISO timestamp is not a cursor: `+00:00`

    decodes to a space in a query string, which silently breaks every second
    page.


    The v2 surface does **not** yet use one cursor name everywhere. `/logs`
    returns

    `next`; `/documents`, `/extractions`, `/reports` and `/knowledge/ingestions`

    return `next_cursor` and read it back as `cursor`;
    `/conversations/{id}/messages`

    returns `next_cursor` and reads it back as `after`, and its cursor is a
    message

    id rather than an encoded pair; `/jobs` pages by `offset` and returns no
    cursor

    at all. Each operation documents what it actually takes.


    ## Idempotency


    Send `Idempotency-Key` on any operation that lists it. Same key and same
    body

    replays the stored response including its status — so some resources replay

    **200** where they first answered **201** or **202**. Same key and a
    different

    body is **409**, never a replay.
  title: Impel Labs API v2
  version: 2.0.0
servers: []
security: []
paths:
  /api/v2/responses/{response_id}/cancel:
    post:
      tags:
        - responses
      description: >-
        `POST /api/v2/responses/{response_id}/cancel` (§30).


        §30 offers this endpoint as optional and attaches one condition — *if

        implemented in Phase 3, it must be safe and idempotent* — so both

        properties are structural rather than checked:


        **Safe.** `resolve_run` runs first and applies the same four bindings

        every other read does, so a guessed id answers 404 whether it names

        nothing or another tenant's run. A run that has already reached a
        terminal

        status is left exactly as it is: the conditional `UPDATE` in

        `runtime.cancellation.request_stop` names the states it is legal to stop

        from, so a run that finished between the read and the write is not

        reopened by a cancel that lost the race.


        **Idempotent.** Stopping is `Event.set()` plus that `UPDATE`; doing
        either

        twice is indistinguishable from doing it once. A second call answers the

        same way as the first, and a call for a finished run answers 200 rather

        than an error, because "it already stopped" is the outcome the caller

        wanted.


        Two statuses, and the difference is information rather than success:

        **202** the run was in flight and will stop at its next checkpoint;

        **200** there was nothing left to stop. Cancellation is cooperative —
        see

        `runtime.cancellation` for what that promises at each point of a turn —

        so 202 is the honest code for "accepted", not 204.


        Any run this caller may read can be cancelled here, not only those from

        `POST /v2/responses`. The path is §30's; the ids are one namespace.


        **Required scope:** `responses:create`. A key without it is a 403 naming
        the scope; the endpoint is never silently skipped.
      operationId: post_responses_by_response_id_cancel
      parameters:
        - in: path
          name: response_id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: No response body
        '202':
          description: >-
            Accepted. The work is queued: poll the job or the resource for its
            status rather than treating this as completion.
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunError'
          description: >-
            The request could not be understood. An unknown key inside `options`
            is refused by name rather than ignored — a silently dropped option
            is three support tickets away from being noticed.


            Codes with this status: `invalid_request`
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunError'
          description: |-
            No credential, or one that does not resolve.

            Codes with this status: `authentication_error`
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunError'
          description: >-
            The credential is valid and does not carry the scope this operation
            demands, or the request context named something the caller does not
            hold. **Never** returned for a resource in another tenant — that is
            404, so that guessing ids cannot become an inventory.


            Codes with this status: `invalid_context`, `permission_denied`
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunError'
          description: >-
            No such resource **for this caller**. A resource that exists in
            another workspace and a resource that does not exist at all answer
            identically, byte for byte.


            Codes with this status: `agent_not_found`, `conversation_not_found`,
            `run_not_found`
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunError'
          description: >-
            Something failed on this side. The response carries a `request_id`;
            nothing else about the failure is disclosed.


            Codes with this status: `internal_error`
      security:
        - CompanyApiKey: []
        - ApplicationCredential: []
        - DashboardSession: []
components:
  schemas:
    RunError:
      additionalProperties: false
      properties:
        error:
          description: >-
            Some endpoints add one extra key beside these three, carrying the
            resource the failure was about. Treat this object as open.
          properties:
            code:
              description: >-
                A stable machine-readable code. Switch on this, never on
                `message`, which is prose and may be reworded without a version
                bump.
              enum:
                - agent_not_available
                - agent_not_found
                - authentication_error
                - conversation_not_found
                - idempotency_conflict
                - internal_error
                - invalid_context
                - invalid_request
                - knowledge_unavailable
                - not_implemented
                - permission_denied
                - provider_unavailable
                - rate_limited
                - run_not_found
                - structured_output_failed
                - tool_execution_failed
                - usage_blocked
              type: string
            message:
              description: >-
                A sentence safe to show a person. It never contains a stack
                trace, a provider name, a database id or any prompt text — the
                diagnostic detail is logged against `request_id` and is not
                serialised.
              type: string
            request_id:
              description: >-
                Quote this in a support ticket. It is also on the `X-Request-Id`
                response header, including on responses that carry no body.
              type: string
          required:
            - code
            - message
          type: object
        id:
          description: >-
            The run id, `run_…`. Present only when the failure ended a run that
            had already started.
          type: string
        status:
          description: >-
            The run's terminal status. `usage_blocked` and `rate_limited` end a
            run as `blocked`, not `failed`: nothing went wrong upstream, the
            platform declined to spend.
          enum:
            - queued
            - running
            - completed
            - failed
            - cancelled
            - blocked
          type: string
        trace_id:
          type: string
      required:
        - error
      type: object
  securitySchemes:
    CompanyApiKey:
      description: >-
        A managed API key, sent as `Authorization: Bearer <key>`. A workspace
        key is prefixed `tgcc_`; a reseller key is prefixed `tgpk_`. Keys carry
        scopes; the scope each operation demands is on the operation as
        `x-required-scope`.
      scheme: bearer
      type: http
    ApplicationCredential:
      description: >-
        An embedded application credential, sent as `Authorization: Bearer
        impa_<environment>_<id>.<secret>`. The environment is part of the
        credential, so a development credential can never read production data.
      scheme: bearer
      type: http
    DashboardSession:
      description: >-
        The dashboard's own session. Listed because the same endpoints serve the
        console; an integration uses a key.
      in: cookie
      name: tg_access
      type: apiKey

````