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

# Get apiv2documents

> `POST /api/v2/documents` (§6) and `GET /api/v2/documents` (§5).

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



## OpenAPI

````yaml /openapi/v2.yaml get /api/v2/documents
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/documents:
    get:
      tags:
        - documents
      description: >-
        `POST /api/v2/documents` (§6) and `GET /api/v2/documents` (§5).


        **Required scope:** `documents:read`. A key without it is a 403 naming
        the scope; the endpoint is never silently skipped.
      operationId: get_documents
      parameters:
        - description: >-
            Page size. Clamped to the endpoint's maximum rather than refused,
            except on `/conversations/{id}/messages`, which answers 400 for a
            limit above 100.
          in: query
          name: limit
          required: false
          schema:
            type: integer
        - description: Filter to one status. An unknown value is a 400, not an empty page.
          in: query
          name: status
          required: false
          schema:
            type: string
        - description: >-
            An opaque cursor. Pass back the value the previous page returned; do
            not construct or parse one. Cursors are base64url and may contain no
            padding — they are safe in a query string, which a raw ISO timestamp
            is not (`+00:00` decodes to a space and silently breaks every second
            page).
          in: query
          name: cursor
          required: false
          schema:
            type: string
      responses:
        '200':
          description: No response body
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: |-
            No credential, or one that does not resolve.

            Codes with this status: `authentication_error`
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          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`
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          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:
    Error:
      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
      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

````