# Generated from openapi.json by scripts/sync-spec.js; do not edit
components:
  headers:
    RateLimit-Limit:
      description: Requests allowed per minute for your account.
      example: 60
      schema:
        type: integer
    RateLimit-Remaining:
      description: Requests left in the current minute window.
      example: 57
      schema:
        type: integer
    RateLimit-Reset:
      description: Seconds until the current minute window resets (or, when the daily token quota is what blocks you, until the quota resets).
      example: 41
      schema:
        type: integer
    Retry-After:
      description: Seconds to wait before retrying.
      example: 23
      schema:
        type: integer
    WWW-Authenticate:
      description: "Always `Bearer`: the request did not authenticate."
      example: Bearer
      schema:
        type: string
    X-Request-ID:
      description: "The request id: your own `X-Request-ID` when it matches `^[a-zA-Z0-9_-]{1,64}$`, otherwise a server-minted 32-character hexadecimal id. Quote it when contacting support."
      example: 8f1c2d3e4b5a69781c0d2e3f4a5b6c7d
      schema:
        type: string
  schemas:
    ChatCompletion:
      description: A chat completion in the OpenAI `chat.completion` shape, plus the `citations` extension on the message.
      properties:
        choices:
          description: Exactly one choice.
          items:
            $ref: "#/components/schemas/Choice"
          type: array
        created:
          description: Unix time in seconds (UTC) when the completion was created.
          type: integer
        id:
          description: "`chatcmpl-` followed by 24 hexadecimal characters."
          type: string
        model:
          description: The canonical slug of the bot that answered, whatever name the request sent.
          type: string
        object:
          const: chat.completion
          default: chat.completion
          type: string
        usage:
          $ref: "#/components/schemas/Usage"
      required:
        - id
        - object
        - created
        - model
        - choices
        - usage
      title: ChatCompletion
      type: object
    ChatCompletionChunk:
      description: "One frame of a streamed chat completion, in the OpenAI `chat.completion.chunk` shape. `id`, `created`, and `model` are identical on every chunk of a stream. The optional usage chunk (requested with `stream_options.include_usage`) has `choices: []` and `usage`."
      properties:
        choices:
          description: One choice, or an empty array on the usage chunk.
          items:
            $ref: "#/components/schemas/ChunkChoice"
          type: array
        created:
          description: Unix time in seconds (UTC), the same on every chunk.
          type: integer
        id:
          description: The completion id, the same on every chunk.
          type: string
        model:
          description: The bot's canonical slug.
          type: string
        object:
          const: chat.completion.chunk
          default: chat.completion.chunk
          type: string
        usage:
          anyOf:
            - $ref: "#/components/schemas/Usage"
            - type: "null"
          description: Present on the usage chunk only.
      required:
        - id
        - created
        - model
        - choices
      title: ChatCompletionChunk
      type: object
    ChatCompletionMessage:
      description: The assistant message of a chat completion. Every key is always present (`null` where it does not apply), as in the OpenAI object.
      properties:
        citations:
          description: "eCourtDate extension. The passages behind the `[Source N]` markers in `content`: an array (possibly empty) on a text answer from a bot with a knowledge base and citations enabled; `null` on a tool-call turn and for bots that do not cite."
          items:
            $ref: "#/components/schemas/Citation"
          type:
            - array
            - "null"
          x-ecd-extension: true
        content:
          description: The answer text, with `[Source N]` markers where it cites the knowledge base. `null` when the model only returned tool calls.
          type:
            - string
            - "null"
        refusal:
          description: Always `null`.
          type: "null"
        role:
          const: assistant
          default: assistant
          type: string
        tool_calls:
          description: Tool calls to run, or `null` when the model called none.
          items:
            $ref: "#/components/schemas/ToolCall"
          type:
            - array
            - "null"
      required:
        - role
        - content
        - refusal
        - tool_calls
        - citations
      title: ChatCompletionMessage
      type: object
    ChatCompletionRequest:
      additionalProperties: true
      description: A chat completion request in the OpenAI shape. Unknown fields are ignored.
      properties:
        max_completion_tokens:
          description: Upper bound on generated tokens. Values above 8192 are clamped to 8192. Omit for the bot's default.
          minimum: 1
          type:
            - integer
            - "null"
        max_tokens:
          description: Legacy name for `max_completion_tokens`; used when that field is omitted.
          minimum: 1
          type:
            - integer
            - "null"
        messages:
          description: The conversation so far, 1 to 200 messages. The bot's knowledge base is searched with the last message when it is a `user` message.
          items:
            $ref: "#/components/schemas/ChatMessageParam"
          maxItems: 200
          minItems: 1
          type: array
        model:
          description: A bot slug or alias from `GET /v1/models`. Whitespace is trimmed; omitted, `null`, blank, or `default` selects your account's default bot. An unknown name is 404 `model_not_found`. The response echoes the bot's canonical slug.
          type:
            - string
            - "null"
        n:
          default: 1
          description: Number of choices. Only `1` is supported.
          type:
            - integer
            - "null"
        response_format:
          description: "Output format. `text` (the default) is free text; `json_object` makes the answer a single JSON object; `json_schema` makes it conform to `json_schema.schema`. The JSON modes cannot be combined with `tools` (400, `param: response_format`)."
          properties:
            json_schema:
              description: Required when `type` is `json_schema`.
              properties:
                description:
                  type: string
                name:
                  description: A name for the schema.
                  minLength: 1
                  type: string
                schema:
                  description: The JSON Schema the answer must satisfy.
                  type: object
                strict:
                  type: boolean
              required:
                - name
                - schema
              type: object
            type:
              enum:
                - text
                - json_object
                - json_schema
              type: string
          required:
            - type
          type:
            - object
            - "null"
        stop:
          anyOf:
            - type: string
            - items:
                type: string
              type: array
            - type: "null"
          description: One or more sequences at which generation stops. An empty string is a 400.
        stream:
          default: false
          description: Stream the answer as server-sent events (`text/event-stream`) instead of one JSON object.
          type: boolean
        stream_options:
          anyOf:
            - $ref: "#/components/schemas/StreamOptions"
            - type: "null"
        temperature:
          description: Sampling temperature, 0 to 2; values above 1 are treated as 1 by the underlying language model. Omit for the model's default. Some models ignore sampling parameters.
          maximum: 2
          minimum: 0
          type:
            - number
            - "null"
        tool_choice:
          description: "How the model uses `tools`: `auto` (the default when tools are sent), `none`, `required`, or one named function. Validated after the message rules and the tool definitions, even without `tools`: any other form is a 400 with `param: tool_choice`, and so is naming a function that `tools` does not declare. A well-formed value is ignored when no `tools` are sent."
          oneOf:
            - enum:
                - auto
                - none
                - required
              type: string
            - properties:
                function:
                  properties:
                    name:
                      description: The name of one of the functions in `tools`.
                      type: string
                  required:
                    - name
                  type: object
                type:
                  enum:
                    - function
                  type: string
              required:
                - type
                - function
              type: object
            - type: "null"
        tools:
          description: "Functions the model may call. Every name must be on the bot's tool allowlist (400 `tool_not_allowed` with `param: tools[i]` otherwise). A malformed entry (`type` other than `function`, a missing or invalid `function.name`, a `function.description` that is not a string, or `function.parameters` that is not an object) is a 400 with `param: tools[i]`. An empty array is the same as omitting the field."
          items:
            description: "A function the model may call. Only `type: function` is supported."
            properties:
              function:
                properties:
                  description:
                    description: What the function does; the model uses it to decide when to call it.
                    type: string
                  name:
                    description: 1 to 64 letters, digits, underscores, or dashes. Must be on the bot's tool allowlist, or the request is 400 `tool_not_allowed`.
                    pattern: ^[a-zA-Z0-9_-]{1,64}$
                    type: string
                  parameters:
                    description: JSON Schema for the function's arguments. Defaults to an object schema with no properties.
                    type: object
                required:
                  - name
                type: object
              type:
                enum:
                  - function
                type: string
            required:
              - type
              - function
            type: object
          type:
            - array
            - "null"
        top_p:
          description: Nucleus sampling, 0 to 1; ignored when `temperature` is also set. Omit for the model's default. Some models ignore sampling parameters.
          maximum: 1
          minimum: 0
          type:
            - number
            - "null"
        user:
          description: An identifier for your end user. Accepted and ignored.
          type:
            - string
            - "null"
      required:
        - messages
      title: ChatCompletionRequest
      type: object
    ChatMessageParam:
      additionalProperties: true
      description: One message of the conversation sent to the model.
      properties:
        content:
          anyOf:
            - type: string
            - items:
                $ref: "#/components/schemas/ContentPartText"
              type: array
            - type: "null"
          description: "The message text: a string, or an array of `text` parts joined with newlines. At most 100,000 characters. Required and non-blank (400, `param: messages[i].content`) on `system`, `developer`, `user`, and `assistant` turns, except an `assistant` turn that carries `tool_calls`, where it may be `null`. A `tool` turn may send `null` or an empty string; both are treated as an empty result."
        name:
          description: Accepted for compatibility and ignored.
          type:
            - string
            - "null"
        role:
          description: "`system` and `developer` text is appended to the bot's own instructions (it can add task guidance but never replaces the bot's grounding rules). The first non-system message must be `user`; the last must be `user` or `tool`."
          enum:
            - system
            - developer
            - user
            - assistant
            - tool
          type: string
        tool_call_id:
          description: "On a `tool` turn (required there): the id of the tool call this message answers, exactly as the response returned it."
          type:
            - string
            - "null"
        tool_calls:
          description: "On an `assistant` turn: the tool calls it made, echoed back from the earlier response so the model can continue after the `tool` results."
          items:
            description: A tool call the assistant made earlier in the conversation, exactly as it was returned in `choices[0].message.tool_calls`.
            properties:
              function:
                properties:
                  arguments:
                    description: The call's arguments as a JSON-encoded string.
                    type: string
                  name:
                    minLength: 1
                    type: string
                required:
                  - name
                  - arguments
                type: object
              id:
                description: The tool call id exactly as the response returned it (`call_` plus an opaque token); a later `tool` message answers it by `tool_call_id`.
                minLength: 1
                type: string
              type:
                enum:
                  - function
                type: string
            required:
              - id
              - type
              - function
            type: object
          type:
            - array
            - "null"
      required:
        - role
      title: ChatMessageParam
      type: object
    Choice:
      description: The single choice of a chat completion (`n` is always 1).
      properties:
        finish_reason:
          default: stop
          description: "`stop`: the model finished; `length`: `max_completion_tokens` was reached; `content_filter`: the model declined; `tool_calls`: run `message.tool_calls` and continue the conversation with `tool` messages."
          enum:
            - stop
            - length
            - content_filter
            - tool_calls
          type: string
        index:
          default: 0
          description: Always `0`.
          type: integer
        logprobs:
          description: Always `null`.
          type: "null"
        message:
          $ref: "#/components/schemas/ChatCompletionMessage"
      required:
        - index
        - message
        - logprobs
        - finish_reason
      title: Choice
      type: object
    ChunkChoice:
      description: The single choice of a streamed chunk.
      properties:
        delta:
          $ref: "#/components/schemas/ChunkDelta"
        finish_reason:
          description: "`null` on every chunk except the finish chunk."
          enum:
            - stop
            - length
            - content_filter
            - tool_calls
            - null
          type:
            - string
            - "null"
        index:
          default: 0
          description: Always `0`.
          type: integer
        logprobs:
          description: Always `null`.
          type: "null"
      required:
        - delta
      title: ChunkChoice
      type: object
    ChunkDelta:
      description: "The incremental content of one streamed chunk. Keys are present only when they carry a value: the first chunk has `role` and an empty `content`, content chunks have `content`, tool chunks have `tool_calls`, and the finish chunk is `{}` or carries `citations`."
      properties:
        citations:
          description: "eCourtDate extension. On the finish chunk of a text answer from a bot with a knowledge base and citations enabled: the passages behind the `[Source N]` markers (possibly an empty array). Absent on tool-call turns and for bots that do not cite."
          items:
            $ref: "#/components/schemas/Citation"
          type:
            - array
            - "null"
          x-ecd-extension: true
        content:
          description: A text fragment. Concatenate fragments in order.
          type:
            - string
            - "null"
        role:
          description: "`assistant`, on the first chunk only."
          enum:
            - assistant
            - null
          type:
            - string
            - "null"
        tool_calls:
          items:
            $ref: "#/components/schemas/ChunkToolCall"
          type:
            - array
            - "null"
      title: ChunkDelta
      type: object
    ChunkToolCall:
      description: One streamed tool-call delta.
      properties:
        function:
          $ref: "#/components/schemas/ChunkToolCallFunction"
        id:
          description: The tool call id (`call_` plus an opaque token), sent on the first delta of a call only.
          type:
            - string
            - "null"
        index:
          description: Zero-based position of the call among this answer's tool calls.
          type: integer
        type:
          description: Sent on the first delta of a call only.
          enum:
            - function
            - null
          type:
            - string
            - "null"
      required:
        - index
        - function
      title: ChunkToolCall
      type: object
    ChunkToolCallFunction:
      description: Function fields of a streamed tool-call delta.
      properties:
        arguments:
          description: An arguments fragment; concatenate per `index`, then parse as JSON.
          type: string
        name:
          description: Sent on the first delta of a call only.
          type:
            - string
            - "null"
      required:
        - arguments
      title: ChunkToolCallFunction
      type: object
    Citation:
      description: "One knowledge-base passage the answer cites. eCourtDate extension to the OpenAI message object: resolves a `[Source N]` marker in the assistant message `content` (`N` is `source_index`)."
      properties:
        chunk_index:
          description: Zero-based position of the cited passage within the document.
          minimum: 0
          type: integer
        document_id:
          description: Id of the cited document (`GET /v1/documents/{documentId}`).
          type: string
        score:
          description: Relevance score of the passage for the question, higher is more relevant.
          type: number
        source_filename:
          description: "The document's `filename`: the upload name, or the page URL for crawled pages."
          type: string
        source_index:
          description: The `N` in the `[Source N]` marker this citation resolves, starting at 1.
          minimum: 1
          type: integer
        text_preview:
          description: The first 200 characters of the cited passage.
          maxLength: 200
          type: string
      required:
        - source_index
        - document_id
        - source_filename
        - chunk_index
        - text_preview
        - score
      title: Citation
      type: object
      x-ecd-extension: true
    ContentPartText:
      additionalProperties: true
      description: One part of an array-form message content. Only text parts are supported.
      properties:
        text:
          description: "The text of this part. Required: a `text` part without it is a 400 with `param: messages[i].content[j].text`."
          type:
            - string
            - "null"
        type:
          const: text
          description: "Only `text` is supported; any other part type is a 400 with `param: messages[i].content[j].type`."
          type: string
      required:
        - type
      title: ContentPartText
      type: object
    Conversation:
      description: "A server-side conversation: its binding to a bot, your metadata, and timestamps."
      properties:
        created:
          description: Unix time in seconds (UTC) when the conversation was created.
          type: integer
        id:
          description: The conversation id (a UUID).
          type: string
        metadata:
          additionalProperties:
            type: string
          description: Your key/value pairs as stored; `{}` when none were sent.
          type: object
        model:
          description: The canonical slug of the bot the conversation is bound to, or `null` when it follows your account's default bot (resolved on each message).
          type:
            - string
            - "null"
        object:
          const: conversation
          default: conversation
          type: string
        updated:
          description: Unix time in seconds (UTC) of the last stored turn; equal to `created` until the first message.
          type: integer
      required:
        - id
        - object
        - model
        - metadata
        - created
        - updated
      title: Conversation
      type: object
    ConversationCreate:
      description: Create a conversation. Both fields are optional. Unknown fields are ignored.
      properties:
        metadata:
          additionalProperties:
            maxLength: 512
            type: string
          description: Up to 16 key/value pairs for your own identifiers (a session id, a case number, the channel). Keys are 1 to 64 characters; values are strings of at most 512 characters. Stored with the conversation and returned by create and retrieve.
          maxProperties: 16
          propertyNames:
            maxLength: 64
            minLength: 1
          type:
            - object
            - "null"
        model:
          description: "A bot slug or alias, resolved exactly as on chat completions, or the literal `default`, which binds the conversation to the canonical slug of the account's default bot at creation time (when that default is the built-in assistant, because no bot is enabled, null is stored instead and the conversation follows the default). Omit, or send null or a blank string, to store null and follow whatever the account's default bot is at the time of each message; that is a 400 with `param: model` when several bots are enabled and no default is configured. Whitespace is trimmed. Responses carry the bound bot's canonical slug, or null for a conversation that follows the default."
          type:
            - string
            - "null"
      title: ConversationCreate
      type: object
    ConversationDetail:
      description: A conversation with its full stored transcript.
      properties:
        created:
          description: Unix time in seconds (UTC) when the conversation was created.
          type: integer
        id:
          description: The conversation id (a UUID).
          type: string
        messages:
          description: Every stored message in order, alternating user and assistant.
          items:
            $ref: "#/components/schemas/ConversationMessage"
          type: array
        metadata:
          additionalProperties:
            type: string
          description: Your key/value pairs as stored; `{}` when none were sent.
          type: object
        model:
          description: The canonical slug of the bot the conversation is bound to, or `null` when it follows your account's default bot (resolved on each message).
          type:
            - string
            - "null"
        object:
          const: conversation
          default: conversation
          type: string
        updated:
          description: Unix time in seconds (UTC) of the last stored turn; equal to `created` until the first message.
          type: integer
      required:
        - id
        - object
        - model
        - metadata
        - created
        - updated
        - messages
      title: ConversationDetail
      type: object
    ConversationMessage:
      description: A stored message of a conversation.
      properties:
        citations:
          description: "eCourtDate extension. On an assistant message: the passages behind the `[Source N]` markers, an array (possibly empty) when the bot has a knowledge base and citations enabled, `null` otherwise. Always `null` on user messages."
          items:
            $ref: "#/components/schemas/Citation"
          type:
            - array
            - "null"
          x-ecd-extension: true
        content:
          description: The message text. Assistant text carries `[Source N]` markers where it cites the knowledge base.
          type: string
        role:
          enum:
            - user
            - assistant
          type: string
      required:
        - role
        - content
        - citations
      title: ConversationMessage
      type: object
    ConversationMessageChunk:
      description: "One frame of a streamed conversation reply: a delta frame while the answer is generated, then one done frame carrying the complete message."
      oneOf:
        - properties:
            conversation_id:
              type: string
            delta:
              properties:
                content:
                  description: A text fragment. Concatenate fragments in order.
                  type: string
              required:
                - content
              type: object
          required:
            - conversation_id
            - delta
          type: object
        - properties:
            conversation_id:
              type: string
            done:
              enum:
                - true
              type: boolean
            message:
              $ref: "#/components/schemas/ConversationMessage"
          required:
            - conversation_id
            - message
            - done
          type: object
      title: ConversationMessageChunk
    ConversationMessageRequest:
      description: A user message to add to a conversation. Unknown fields are ignored.
      properties:
        content:
          description: "The user message, 1 to 100,000 characters. Not trimmed, but it must not be blank: whitespace-only content is a 400 (`param: content`), as on chat completions."
          maxLength: 100000
          minLength: 1
          type: string
        stream:
          default: false
          description: Stream the assistant reply as server-sent events.
          type: boolean
      required:
        - content
      title: ConversationMessageRequest
      type: object
    ConversationMessageResponse:
      description: The assistant's reply to a conversation message. The user message and this reply are both stored on the conversation.
      properties:
        conversation_id:
          description: The conversation the turn was stored on.
          type: string
        message:
          $ref: "#/components/schemas/ConversationMessage"
          description: The assistant reply.
      required:
        - conversation_id
        - message
      title: ConversationMessageResponse
      type: object
    CrawlCompletedEvent:
      description: Sent once when a crawl job reaches a terminal status. Mirrors the job object from `GET /v1/ingest/crawl/{crawlJobId}` plus `event` and `namespace`.
      properties:
        crawl_job_id:
          description: The crawl job id (a UUID).
          type: string
        errors:
          description: One entry per failed page (each has a `failed` document carrying the same error, except a page that answered with an HTTP error status, which creates no document), or one `crawl` entry when the crawl itself failed or timed out (a timeout also fails every document of the crawl still processing).
          items:
            $ref: "#/components/schemas/CrawlError"
          type: array
        event:
          const: crawl.completed
          description: The event name, also sent as `X-ECD-Event`.
          type: string
        namespace:
          description: The knowledge-base namespace that received the pages.
          type: string
        pages_crawled:
          description: Pages fetched successfully, including pages with no text; pages that answered with an HTTP error status are not counted.
          type: integer
        pages_indexed:
          description: Pages that became ready documents.
          type: integer
        status:
          description: "completed: finished with no page errors; completed_with_errors: some pages indexed and some failed; failed: nothing indexed, or the crawl itself failed (a crawl that timed out after indexing some pages is `failed` with `pages_indexed` above zero)."
          enum:
            - completed
            - completed_with_errors
            - failed
          type: string
      required:
        - event
        - crawl_job_id
        - namespace
        - status
        - pages_crawled
        - pages_indexed
        - errors
      title: CrawlCompletedEvent
      type: object
    CrawlError:
      description: One page that failed, or the whole crawl.
      properties:
        error:
          description: Sanitized failure reason. `HTTP <status>` for a page the site answered with an error status (it is not indexed); `Crawl timed out.` under the `crawl` sentinel when the 10-minute budget ran out.
          type: string
        url:
          description: The page URL, or the sentinel 'crawl' for a whole-crawl failure.
          type: string
      required:
        - url
        - error
      title: CrawlError
      type: object
    CrawlJobStatus:
      description: The state of a crawl job and its page counts.
      properties:
        crawl_job_id:
          description: The crawl job id (a UUID).
          type: string
        errors:
          description: One entry per failed page (each has a `failed` document carrying the same error, except a page that answered with an HTTP error status, which creates no document), or one `crawl` entry when the crawl itself failed or timed out (a timeout also fails every document of the crawl still processing).
          items:
            $ref: "#/components/schemas/CrawlError"
          type: array
        pages_crawled:
          description: Pages fetched successfully so far, including pages with no text; pages that answered with an HTTP error status are not counted.
          type: integer
        pages_indexed:
          description: Pages that became ready documents so far.
          type: integer
        status:
          description: "processing: queued or running; completed: finished with no page errors; completed_with_errors: some pages indexed and some failed; failed: nothing indexed, or the crawl itself failed (exception, timeout, cancellation). `failed` describes the crawl as a whole: a crawl that timed out after indexing some pages is `failed` with `pages_indexed` above zero, and those documents stay ready. The counts and `errors` are updated after every page, so they are live while `processing`."
          enum:
            - processing
            - completed
            - completed_with_errors
            - failed
          type: string
      required:
        - crawl_job_id
        - status
        - pages_crawled
        - pages_indexed
        - errors
      title: CrawlJobStatus
      type: object
    CrawlRequest:
      description: A website crawl to run. Unknown fields are ignored.
      properties:
        allowed_domains:
          default: []
          description: "Hosts the crawler may fetch from: an exact, case-sensitive match on the URL's host, port included, except that a scheme's default port is not significant (`example.com:443` is `example.com` for https, `example.com:80` for http). Empty derives the allowlist from the seed URLs' hosts."
          items:
            type: string
          type: array
        max_depth:
          default: 3
          description: How many link hops to follow from the seed URLs. Seeds are depth 0, so `1` fetches the seeds and the pages they link to, `2` one hop further, and so on. `max_pages` caps the total either way.
          maximum: 10
          minimum: 1
          type: integer
        max_pages:
          default: 50
          description: Stop after this many pages have been fetched successfully (pages that answer with an HTTP error status do not count). The whole crawl, indexing included, must fit the 10-minute budget.
          maximum: 500
          minimum: 1
          type: integer
        namespace:
          description: Knowledge-base namespace that receives the crawled documents.
          pattern: ^[a-z0-9][a-z0-9_-]{0,63}$
          type: string
        rate_limit_rps:
          default: 2
          description: Maximum fetches per second against the crawled site.
          maximum: 10
          minimum: 0.1
          type: number
        respect_robots_txt:
          default: true
          description: Honor the site's robots.txt (disallowed paths are skipped).
          type: boolean
        seed_urls:
          description: 1 to 100 public http(s) URLs to start from.
          items:
            type: string
          maxItems: 100
          minItems: 1
          type: array
      required:
        - namespace
        - seed_urls
      title: CrawlRequest
      type: object
    CrawlResponse:
      description: "An accepted crawl: the job to poll."
      properties:
        crawl_job_id:
          description: UUID of the crawl job; poll `GET /v1/ingest/crawl/{crawlJobId}`.
          type: string
        status:
          const: processing
          default: processing
          description: Always `processing` at submission.
          type: string
      required:
        - crawl_job_id
        - status
      title: CrawlResponse
      type: object
    Document:
      description: "A document in your knowledge base: an uploaded file or a crawled page."
      properties:
        chunk_count:
          default: 0
          description: Indexed chunks; > 0 only when ready.
          type: integer
        created_at:
          description: "When the document was created: RFC 3339 UTC with a `Z` suffix, always in the form `2026-08-21T14:03:11.214000Z`. Millisecond precision, rendered with six fractional digits (the last three are always `000`)."
          examples:
            - 2026-08-21T20:15:03.123000Z
          format: date-time
          type: string
        error:
          description: Sanitized failure reason when status is failed (same text as the job's errors[] entry); null otherwise.
          type:
            - string
            - "null"
        filename:
          description: Sanitized upload filename, or the final page URL for crawled pages.
          type: string
        id:
          description: The document id (a UUID).
          type: string
        namespace:
          description: The knowledge-base namespace the document was ingested into.
          type: string
        object:
          const: document
          default: document
          type: string
        source_type:
          description: "`upload` for a file sent to `POST /v1/ingest/files`; `crawl` for a crawled page."
          enum:
            - upload
            - crawl
          type: string
        source_url:
          description: Final page URL for crawled pages; `null` for uploads.
          type:
            - string
            - "null"
        status:
          description: "processing: waiting for or undergoing extraction and indexing (also between automatic retries); ready: indexed and citable; failed: permanently failed, see error."
          enum:
            - processing
            - ready
            - failed
          type: string
        updated_at:
          description: When the document last changed status, in the same form as `created_at`.
          examples:
            - 2026-08-21T20:15:03.123000Z
          format: date-time
          type: string
      required:
        - id
        - object
        - namespace
        - filename
        - source_type
        - source_url
        - status
        - chunk_count
        - error
        - created_at
        - updated_at
      title: Document
      type: object
    DocumentDeleted:
      description: Confirmation that a document and its indexed passages were removed.
      properties:
        chunks_deleted:
          default: 0
          description: Indexed passages removed with the document.
          type: integer
        deleted:
          default: true
          description: Always `true`.
          type: boolean
        id:
          description: The deleted document's id.
          type: string
        object:
          const: document.deleted
          default: document.deleted
          type: string
      required:
        - id
        - object
        - deleted
        - chunks_deleted
      title: DocumentDeleted
      type: object
    DocumentList:
      description: A page of documents, newest first, in the OpenAI list envelope with cursor pagination.
      properties:
        data:
          description: Up to `limit` documents.
          items:
            $ref: "#/components/schemas/Document"
          type: array
        first_id:
          description: id of the first item in data; null when empty.
          type:
            - string
            - "null"
        has_more:
          description: true when more documents follow the last item in data.
          type: boolean
        last_id:
          description: id of the last item in data (pass as `after` for the next page); null when empty.
          type:
            - string
            - "null"
        object:
          const: list
          default: list
          type: string
      required:
        - object
        - data
        - first_id
        - last_id
        - has_more
      title: DocumentList
      type: object
    Embedding:
      description: One embedding, in input order.
      properties:
        embedding:
          anyOf:
            - items:
                type: number
              type: array
            - type: string
          description: "1024 numbers (`encoding_format: float`) or a base64 string of 1024 little-endian 32-bit floats (`encoding_format: base64`)."
        index:
          description: Position of the input this embedding belongs to.
          type: integer
        object:
          const: embedding
          default: embedding
          type: string
      required:
        - object
        - index
        - embedding
      title: Embedding
      type: object
    EmbeddingList:
      description: Embeddings in the OpenAI list envelope.
      properties:
        data:
          description: One embedding per input, in input order.
          items:
            $ref: "#/components/schemas/Embedding"
          type: array
        model:
          description: The `model` label from the request, or `default`.
          type: string
        object:
          const: list
          default: list
          type: string
        usage:
          $ref: "#/components/schemas/EmbeddingUsage"
      required:
        - object
        - data
        - model
        - usage
      title: EmbeddingList
      type: object
    EmbeddingRequest:
      additionalProperties: true
      description: An embeddings request in the OpenAI shape. Unknown fields are ignored.
      properties:
        dimensions:
          description: Embeddings are always 1024-dimensional. Any other value is a 400.
          type:
            - integer
            - "null"
        encoding_format:
          default: float
          description: "`float`: each embedding is an array of numbers; `base64`: each embedding is a base64 string of little-endian 32-bit floats (what the OpenAI SDKs request by default)."
          enum:
            - float
            - base64
          type: string
        input:
          description: "The text to embed: one string, or an array of 1 to 96 strings. Each string is 1 to 100,000 characters. An offending item is reported with `param: input[i]`. Token arrays are not accepted."
          oneOf:
            - maxLength: 100000
              minLength: 1
              type: string
            - items:
                maxLength: 100000
                minLength: 1
                type: string
              maxItems: 96
              minItems: 1
              type: array
        input_type:
          default: search_document
          description: eCourtDate extension. `search_document` for text you will store and search over; `search_query` for a question you will match against stored embeddings.
          enum:
            - search_document
            - search_query
          type: string
          x-ecd-extension: true
        model:
          description: A label echoed back unchanged in the response (`default` when omitted). The API serves one text embedding model, so the value does not select anything.
          type:
            - string
            - "null"
        user:
          description: An identifier for your end user. Accepted and ignored.
          type:
            - string
            - "null"
      required:
        - input
      title: EmbeddingRequest
      type: object
    EmbeddingUsage:
      description: Token usage of an embeddings request; counts toward your daily token quota.
      properties:
        prompt_tokens:
          default: 0
          description: Input tokens across every input.
          type: integer
        total_tokens:
          default: 0
          description: Equal to `prompt_tokens`.
          type: integer
      required:
        - prompt_tokens
        - total_tokens
      title: EmbeddingUsage
      type: object
    Error:
      description: The error envelope returned by every error response on every route, in the OpenAI shape. Program against the HTTP status and `code`; `message` wording may change.
      properties:
        error:
          properties:
            code:
              description: Stable machine-readable code, or `null` for validation errors and unexpected server errors. `invalid_api_key` (401); `insufficient_scope` (403); `tool_not_allowed` (400); `model_not_found` and `not_found` (404); `request_too_large` (413); `rate_limit_exceeded` and `insufficient_quota` (429); `upstream_unavailable` (503). New codes may be added; fall back on the HTTP status for an unknown code.
              enum:
                - invalid_api_key
                - insufficient_scope
                - tool_not_allowed
                - model_not_found
                - not_found
                - request_too_large
                - rate_limit_exceeded
                - insufficient_quota
                - upstream_unavailable
                - null
              type:
                - string
                - "null"
            message:
              description: Human-readable explanation. Do not branch on it.
              type: string
            param:
              description: The request field at fault in bracket form (`messages[2].tool_call_id`, `tools[0]`, `input[3]`, `after`), or `null` when no single field is responsible.
              type:
                - string
                - "null"
            type:
              description: "`authentication_error` (401); `invalid_request_error` (400, 403, 404, 405, 413); `rate_limit_error` (429); `server_error` (500, 503)."
              enum:
                - invalid_request_error
                - authentication_error
                - rate_limit_error
                - server_error
              type: string
          required:
            - message
            - type
            - param
            - code
          type: object
      required:
        - error
      title: Error
      type: object
    HealthResponse:
      description: Liveness status.
      properties:
        status:
          const: ok
          default: ok
          description: Always `ok` when the API is serving.
          type: string
      required:
        - status
      title: HealthResponse
      type: object
    IngestCompletedEvent:
      description: Sent once when an ingest job reaches a terminal status. Mirrors the job object from `GET /v1/ingest/jobs/{jobId}` plus `event` and `namespace`.
      properties:
        completed_documents:
          description: Documents that reached status ready.
          type: integer
        errors:
          description: One entry per failed document. A document deleted while the job was running is recorded as `Document was deleted before it was processed.` and has no document left to carry the error.
          items:
            $ref: "#/components/schemas/JobError"
          type: array
        event:
          const: ingest.completed
          description: The event name, also sent as `X-ECD-Event`.
          type: string
        failed_documents:
          description: Documents that ended in status failed, including any document deleted while the job was running.
          type: integer
        job_id:
          description: The ingest job id (a UUID).
          type: string
        namespace:
          description: The knowledge-base namespace the files were ingested into.
          type: string
        status:
          description: "completed: every document is ready; completed_with_errors: some documents are ready and some failed; failed: no document is ready."
          enum:
            - completed
            - completed_with_errors
            - failed
          type: string
        total_documents:
          description: Documents in the job (one per uploaded file).
          type: integer
      required:
        - event
        - job_id
        - namespace
        - status
        - total_documents
        - completed_documents
        - failed_documents
        - errors
      title: IngestCompletedEvent
      type: object
    IngestFilesRequest:
      description: "The `multipart/form-data` body of `POST /v1/ingest/files`: one `file` part per file and an optional `namespace` field."
      properties:
        file:
          description: "1 to 20 files, one `file` part each, 25 MiB (26,214,400 bytes) per file. Accepted types, decided from the extension and confirmed by content: .csv, .docx, .eml, .htm, .html, .md, .pdf, .txt, .xlsx. Filenames are at most 255 bytes with no path separators, control characters, or other non-printable characters (for example a non-breaking space or a zero-width joiner); ordinary spaces and printable non-ASCII characters are kept as sent."
          items:
            contentMediaType: application/octet-stream
            type: string
          type: array
        namespace:
          default: general
          description: Knowledge-base namespace that receives the documents. Lowercase letters, digits, `_` and `-`, 1 to 64 characters (`^[a-z0-9][a-z0-9_-]{0,63}$`). Defaults to `general` when omitted; an empty value is a 400.
          pattern: ^[a-z0-9][a-z0-9_-]{0,63}$
          type: string
      required:
        - file
      title: IngestFilesRequest
      type: object
    IngestFilesResponse:
      description: "An accepted upload: the job to poll and the documents it will index."
      properties:
        document_ids:
          description: UUID of the document created for each file, in the order sent.
          items:
            type: string
          type: array
        job_id:
          description: UUID of the ingest job; poll `GET /v1/ingest/jobs/{jobId}`.
          type: string
        status:
          const: processing
          default: processing
          description: Always `processing` at submission.
          type: string
      required:
        - job_id
        - document_ids
        - status
      title: IngestFilesResponse
      type: object
    JobError:
      description: One document that failed to index.
      properties:
        document_id:
          description: The failed document's id.
          type: string
        error:
          description: Sanitized failure reason (same text as the document's `error`).
          type: string
      required:
        - document_id
        - error
      title: JobError
      type: object
    JobStatus:
      description: The state of an ingest job and its per-document outcome.
      properties:
        completed_documents:
          description: Documents that reached status ready so far.
          type: integer
        errors:
          description: One entry per failed document. A document deleted while the job was running is recorded here as `Document was deleted before it was processed.` (there is no document left to carry the error).
          items:
            $ref: "#/components/schemas/JobError"
          type: array
        failed_documents:
          description: Documents that ended in status failed so far, including any document deleted while the job was running.
          type: integer
        job_id:
          description: The ingest job id (a UUID).
          type: string
        status:
          description: "processing: queued or running (also between automatic retries); completed: every document is ready; completed_with_errors: some documents are ready and some failed; failed: no document is ready. The counts and `errors` are updated after every document, so they are live while `processing` and final once the status is terminal."
          enum:
            - processing
            - completed
            - completed_with_errors
            - failed
          type: string
        total_documents:
          description: Documents in the job (one per uploaded file).
          type: integer
      required:
        - job_id
        - status
        - total_documents
        - completed_documents
        - failed_documents
        - errors
      title: JobStatus
      type: object
    Model:
      description: "A model your key can use: a bot slug, an alias, or `default`."
      properties:
        created:
          description: Unix time in seconds (UTC) when the bot was created. For an alias, its target bot's; for `default`, the default bot's.
          type: integer
        id:
          description: "The name to send as `model`: a bot slug, an alias, or `default`."
          type: string
        object:
          const: model
          default: model
          type: string
        owned_by:
          description: Your account id.
          type: string
      required:
        - id
        - object
        - created
        - owned_by
      title: Model
      type: object
    ModelList:
      description: The models available to your key, in the OpenAI list envelope.
      properties:
        data:
          description: "Every enabled bot, then every alias whose target is enabled, then `default` whenever the account default resolves: a configured default bot, a single enabled bot, or the built-in assistant when no bot is enabled. `default` is absent only when several bots are enabled and none is the default."
          items:
            $ref: "#/components/schemas/Model"
          type: array
        object:
          const: list
          default: list
          type: string
      required:
        - object
        - data
      title: ModelList
      type: object
    StreamOptions:
      additionalProperties: true
      description: "Options for streamed responses. Validated on every request (a non-boolean `include_usage` is a 400 even with `stream: false`) but acted on only when `stream` is `true`."
      properties:
        include_usage:
          default: false
          description: "Send one extra chunk after the finish chunk with `choices: []` and the stream's `usage`."
          type: boolean
      title: StreamOptions
      type: object
    ToolCall:
      description: A function call the model wants your application to run.
      properties:
        function:
          $ref: "#/components/schemas/ToolCallFunction"
        id:
          description: "`call_` followed by an opaque token. Pass it back unchanged: as `id` on the echoed assistant turn and as `tool_call_id` on the `tool` message that answers it."
          type: string
        type:
          const: function
          default: function
          type: string
      required:
        - id
        - type
        - function
      title: ToolCall
      type: object
    ToolCallFunction:
      description: The function the model asked to call.
      properties:
        arguments:
          description: The arguments as a JSON-encoded string. Parse it before use.
          type: string
        name:
          description: The function name, as declared in `tools`.
          type: string
      required:
        - name
        - arguments
      title: ToolCallFunction
      type: object
    Usage:
      description: Token usage of a chat completion; counts toward your daily token quota.
      properties:
        completion_tokens:
          default: 0
          description: Generated tokens.
          type: integer
        prompt_tokens:
          default: 0
          description: Input tokens, including the bot's instructions and retrieved passages.
          type: integer
        total_tokens:
          default: 0
          description: "`prompt_tokens` plus `completion_tokens`."
          type: integer
      required:
        - prompt_tokens
        - completion_tokens
        - total_tokens
      title: Usage
      type: object
  securitySchemes:
    bearerAuth:
      bearerFormat: ecd_sk_<key>
      description: "Your account's API key, issued and activated by eCourtDate, sent as `Authorization: Bearer ecd_sk_...`. Keys carry the scopes `chat` and/or `ingest`; each operation states the scope it needs as `x-scope`."
      scheme: bearer
      type: http
info:
  contact:
    name: eCourtDate
    url: https://www.ecourtdate.com
  description: |
    Grounded, cited answers for court and agency websites, call centers, and
    portals. Each bot on your account answers from its own **knowledge base** of
    uploaded documents and crawled web pages, with the personality and rules
    eCourtDate configures for it, and cites the passages it used.

    The API is **OpenAI-compatible**: point the official OpenAI SDKs at
    `https://api.chatbots.ecourtdate.com/v1` with your API key and use chat
    completions, models, and embeddings unchanged. Server-side **conversations**,
    **document management**, file **ingestion**, website **crawling**, and
    completion **webhooks** extend that surface in the same style.

    Access requires a paid subscription. API keys are issued and activated for
    your account by eCourtDate.

    ## Authentication

    Every `/v1` request carries your key in the `Authorization` header:

    ```
    Authorization: Bearer ecd_sk_...
    ```

    A missing, malformed, unknown, expired, or revoked key is `401` with
    `code: invalid_api_key` and a `WWW-Authenticate: Bearer` header. Each key
    carries scopes that gate what it may call; a key without the scope an
    operation needs is `403` with `code: insufficient_scope`. Every operation
    states its scope as `x-scope`.

    | Scope | Grants |
    |---|---|
    | `chat` | Chat completions, models, embeddings, conversations |
    | `ingest` | File uploads, website crawls, ingest and crawl jobs, document management |

    `GET /health` is the only operation that needs no key.

    ## Quickstart

    Ask your default bot a question:

    ```bash
    curl "https://api.chatbots.ecourtdate.com/v1/chat/completions" \
      -H "Authorization: Bearer $API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "messages": [
          { "role": "user", "content": "When is my traffic hearing, and what happens if I miss it?" }
        ]
      }'
    ```

    The same call with the OpenAI Python SDK:

    ```python
    from openai import OpenAI
    client = OpenAI(api_key=API_KEY, base_url="https://api.chatbots.ecourtdate.com/v1")
    print(client.chat.completions.create(model="court-assistant", messages=[{"role": "user", "content": "What are the Traffic Division hours?"}]).choices[0].message.content)
    ```

    `model` is a bot slug or alias from `GET /v1/models`; omit it to use your
    account's default bot. The answer cites the knowledge base with `[Source N]`
    markers that the `citations` array on the message resolves to documents.

    ## Conventions

    - **JSON only.** Request bodies are JSON with `Content-Type: application/json`
      (the one exception is `POST /v1/ingest/files`, which is
      `multipart/form-data`). Responses are JSON, except streamed answers
      (`text/event-stream`) and `DELETE /v1/conversations/{conversationId}`
      (`204`, no body).
    - **One error envelope.** Every error, on every route, is
      `{"error": {"message", "type", "param", "code"}}` (the `Error` schema).
      Program against the HTTP status and `code`; `param` names the request
      field at fault in bracket form (`messages[2].tool_call_id`, `tools[0]`,
      `input[3]`) or is `null`.
    - **Request ids.** Every response carries `X-Request-ID`. Send your own
      (`^[a-zA-Z0-9_-]{1,64}$`) and it is echoed back; otherwise a 32-character
      hexadecimal id is minted. Quote it when contacting support.
    - **Rate limit headers.** Every response to an authenticated `/v1` request
      carries `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`
      (requests allowed per minute, requests left in the current window, seconds
      until it resets): success responses and `400`, `404`, `429`, `500`, and
      `503` alike. They are absent when the request is rejected before
      authentication completes: `401`, `403`, `413`, a body that is not valid
      JSON (`400`), and a malformed `Content-Length` header (`400`). They are
      also absent while the rate limiter itself is unavailable: the request is
      then served without them, or, when the API is configured to fail closed,
      rejected with `429` `Rate limiter unavailable.` carrying `Retry-After` only.
    - **CORS preflight** responses (`OPTIONS` with `Origin` and
      `Access-Control-Request-Method`) are outside this contract: they carry no
      `X-Request-ID` and no error envelope. Browser-direct use is not supported.
    - **Malformed framing.** A request whose `Content-Length` header is not a
      whole number is `400` with the message `Invalid Content-Length header.`,
      `param: null`, and no `code`, on every route and before authentication.
    - **Retry-After.** Every `429` and `503` carries `Retry-After` in seconds.
    - **Strict typing.** Request fields are not coerced from strings:
      `"stream": "yes"` and `"temperature": "0.5"` are `400`s. Integers are
      accepted where a number is expected.
    - **Unknown fields** in request bodies are ignored. Unknown response fields
      may appear at any time; ignore what you do not recognize.
    - **Identifiers** are opaque, case-sensitive strings: conversation,
      document, and job ids are UUIDs; chat completion ids are `chatcmpl-` plus
      24 hexadecimal characters; model ids are bot slugs or aliases.
    - **Timestamps** are Unix seconds (UTC) on the OpenAI-style objects
      (`created`, `updated`) and RFC 3339 UTC on documents (`created_at`,
      `updated_at`), always in one form: six fractional digits and a `Z` suffix
      (`2026-08-20T09:15:00.000000Z`).

    ## Streaming

    Set `"stream": true` on `POST /v1/chat/completions` or
    `POST /v1/conversations/{conversationId}/messages` and the answer arrives as
    server-sent events. Each frame is `data: ` followed by one compact JSON
    document and a blank line; the stream ends with `data: [DONE]`. Chat
    completions stream OpenAI `chat.completion.chunk` objects (the finish chunk
    carries `citations`; an optional usage chunk follows when
    `stream_options.include_usage` is set), so the OpenAI SDKs consume them
    unchanged. A failure after the stream has started is reported in-band as a
    frame carrying the error envelope, followed by `data: [DONE]`.

    ## Rate limits

    Two per-account limits apply to every `/v1` request, shared by all keys on
    the account: a fixed-window **requests per minute** limit and a **daily
    token quota** (input plus output tokens of chat completions, conversation
    messages, and embeddings, reset at midnight UTC). Exceeding either is `429`
    with `type: rate_limit_error`, `code: rate_limit_exceeded` or
    `insufficient_quota`, and `Retry-After`. Rejected requests count toward the
    minute window. Contact eCourtDate to change your limits.

    ## Versioning

    The API is versioned in the path (`/v1`). Within `/v1`, existing fields,
    their types and meanings, status codes, the error envelope, and documented
    paths are stable. Additive changes (new optional request fields, new
    response fields, new operations, new enum values) ship without notice and
    are not breaking: clients must ignore unknown fields. Anything retired is
    announced at least 12 months ahead and signaled with `Deprecation` and
    `Sunset` response headers in the meantime.
  title: eCourtDate Chatbot API
  version: 2.0.0
openapi: 3.1.0
paths:
  /health:
    get:
      description: 'Liveness check for monitoring: returns `{"status": "ok"}` whenever the API is serving requests. It needs no API key, touches no dependency, and is not rate limited. It is the only route outside `/v1`.'
      operationId: health
      responses:
        "200":
          content:
            application/json:
              examples:
                ok:
                  value:
                    status: ok
              schema:
                $ref: "#/components/schemas/HealthResponse"
          description: The API is up.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                content_length:
                  summary: The malformed Content-Length case shared by every route
                  value:
                    error:
                      code: null
                      message: Invalid Content-Length header.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Only the malformed `Content-Length` case that applies to every route (see Conventions in the introduction); this operation validates nothing of its own.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      security: []
      summary: Health check
      tags:
        - Status
  /v1/chat/completions:
    post:
      description: |-
        Answer a conversation with the bot named by `model`, grounded in the bot's knowledge base.

        The request and response follow the OpenAI chat completions shape, so the official OpenAI SDKs work unchanged. `model` is a bot slug or alias from `GET /v1/models`; omit it to use your account's default bot. When the last message is a `user` message, the bot's knowledge base is searched with it and the most relevant passages are given to the model; a text answer then cites them with `[Source N]` markers resolved by the `citations` extension field on the message. `system` and `developer` messages are appended to the bot's own instructions.

        Tools: send `tools` (function definitions on the bot's allowlist) and the model may answer with `tool_calls` and `finish_reason: tool_calls`; run them, append the assistant turn and one `tool` message per call, and call again. `response_format` selects plain text, a JSON object, or JSON matching a schema; JSON modes cannot be combined with tools.

        Set `stream: true` for server-sent events: the response is `text/event-stream` with `chat.completion.chunk` frames and ends with `data: [DONE]`. Add `stream_options.include_usage` for a final usage chunk.

        Errors: 400 for an invalid body (the first failing field in `param`), a blank `system`, `developer`, `user`, or `assistant` message, a `tool` message whose `tool_call_id` does not answer a call of the assistant message before it (`param: messages[i].tool_call_id`), `n` other than 1, an empty `stop` sequence, a malformed tool definition, `tool_choice`, or `response_format`, a tool outside the bot's allowlist (`tool_not_allowed`), or an omitted `model` when the account has several bots and no default; 404 `model_not_found` for an unknown, disabled, or foreign `model`; 429 when the per-minute request limit, the daily token quota, or the underlying language model is exhausted; 503 `upstream_unavailable` when the model or the embedding service fails. Usage counts toward your daily token quota.
      operationId: createChatCompletion
      requestBody:
        content:
          application/json:
            examples:
              hearing_question:
                summary: A grounded question
                value:
                  messages:
                    - content: What are the Traffic Division hours?
                      role: user
                  model: court-assistant
              json_schema:
                summary: Structured output
                value:
                  messages:
                    - content: What is the filing fee for a small claims case under $2,500?
                      role: user
                  model: court-assistant
                  response_format:
                    json_schema:
                      name: filing_fee
                      schema:
                        properties:
                          fee_usd:
                            type: number
                          source:
                            type: string
                        required:
                          - fee_usd
                        type: object
                    type: json_schema
              streaming:
                summary: Streamed, with a usage chunk
                value:
                  messages:
                    - content: What are the Traffic Division hours?
                      role: user
                  model: court-assistant
                  stream: true
                  stream_options:
                    include_usage: true
              tool_result:
                summary: Continuing after a tool call
                value:
                  messages:
                    - content: When is the next hearing for case CR-2026-0412?
                      role: user
                    - content: null
                      role: assistant
                      tool_calls:
                        - function:
                            arguments: '{"case_number": "CR-2026-0412"}'
                            name: lookup_hearing
                          id: call_01Hx7Qm2xT9vL4nR8sW1pB6c
                          type: function
                    - content: '{"hearing_date": "2026-09-14", "time": "9:00 a.m.", "courtroom": "3B"}'
                      role: tool
                      tool_call_id: call_01Hx7Qm2xT9vL4nR8sW1pB6c
                  model: clerk-tools
                  tools:
                    - function:
                        description: Look up the next hearing date for a case number.
                        name: lookup_hearing
                        parameters:
                          properties:
                            case_number:
                              type: string
                          required:
                            - case_number
                          type: object
                      type: function
              tools:
                summary: Offering a tool
                value:
                  messages:
                    - content: When is the next hearing for case CR-2026-0412?
                      role: user
                  model: clerk-tools
                  tool_choice: auto
                  tools:
                    - function:
                        description: Look up the next hearing date for a case number.
                        name: lookup_hearing
                        parameters:
                          properties:
                            case_number:
                              type: string
                          required:
                            - case_number
                          type: object
                      type: function
            schema:
              $ref: "#/components/schemas/ChatCompletionRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              examples:
                text_answer:
                  summary: A cited text answer
                  value:
                    choices:
                      - finish_reason: stop
                        index: 0
                        logprobs: null
                        message:
                          citations:
                            - chunk_index: 2
                              document_id: 17136f01-972d-4956-868e-8159833072e4
                              score: 0.93
                              source_filename: traffic-division-faq.pdf
                              source_index: 1
                              text_preview: "Traffic Division hours: Monday through Friday, 8:00 a.m. to 4:30 p.m. Payments are accepted at the clerk's window until 4:00 p.m."
                          content: The Traffic Division is open Monday through Friday, 8:00 a.m. to 4:30 p.m. Payments at the clerk's window are accepted until 4:00 p.m. [Source 1]
                          refusal: null
                          role: assistant
                          tool_calls: null
                    created: 1787320991
                    id: chatcmpl-8a317b1a95a74321b73b6567
                    model: court-assistant
                    object: chat.completion
                    usage:
                      completion_tokens: 143
                      prompt_tokens: 812
                      total_tokens: 955
                tool_call:
                  summary: A tool call to run
                  value:
                    choices:
                      - finish_reason: tool_calls
                        index: 0
                        logprobs: null
                        message:
                          citations: null
                          content: null
                          refusal: null
                          role: assistant
                          tool_calls:
                            - function:
                                arguments: '{"case_number": "CR-2026-0412"}'
                                name: lookup_hearing
                              id: call_01Hx7Qm2xT9vL4nR8sW1pB6c
                              type: function
                    created: 1787320991
                    id: chatcmpl-8a317b1a95a74321b73b6567
                    model: clerk-tools
                    object: chat.completion
                    usage:
                      completion_tokens: 143
                      prompt_tokens: 812
                      total_tokens: 955
              schema:
                $ref: "#/components/schemas/ChatCompletion"
            text/event-stream:
              examples:
                cited_answer:
                  summary: A full stream with citations and usage
                  value: |+
                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[{"index":0,"delta":{"content":"The Traffic Division is open Monday through Friday, 8:00 a.m. to 4:30 p.m. [Sou"},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[{"index":0,"delta":{"content":"rce 1]"},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[{"index":0,"delta":{"citations":[{"source_index":1,"document_id":"17136f01-972d-4956-868e-8159833072e4","source_filename":"traffic-division-faq.pdf","chunk_index":2,"text_preview":"Traffic Division hours: Monday through Friday, 8:00 a.m. to 4:30 p.m. Payments are accepted at the clerk's window until 4:00 p.m.","score":0.93}]},"logprobs":null,"finish_reason":"stop"}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[],"usage":{"prompt_tokens":812,"completion_tokens":143,"total_tokens":955}}

                    data: [DONE]

                in_band_error:
                  summary: A failure after the stream started
                  value: |+
                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"court-assistant","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}

                    data: {"error":{"message":"The model is currently rate limited. Please retry shortly.","type":"rate_limit_error","param":null,"code":"rate_limit_exceeded"}}

                    data: [DONE]

                tool_call:
                  summary: A streamed tool call
                  value: |+
                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"clerk-tools","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"clerk-tools","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_01Hx7Qm2xT9vL4nR8sW1pB6c","type":"function","function":{"name":"lookup_hearing","arguments":""}}]},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"clerk-tools","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"case_number\": "}}]},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"clerk-tools","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"CR-2026-0412\"}"}}]},"logprobs":null,"finish_reason":null}]}

                    data: {"id":"chatcmpl-8a317b1a95a74321b73b6567","object":"chat.completion.chunk","created":1787320991,"model":"clerk-tools","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}

                    data: [DONE]

              schema:
                contentMediaType: text/event-stream
                contentSchema:
                  $ref: "#/components/schemas/ChatCompletionChunk"
                description: 'Server-sent events. Every frame is `data: `, one compact JSON `chat.completion.chunk` object (the `ChatCompletionChunk` schema), and a blank line; the stream ends with `data: [DONE]`. In order: one role chunk (`delta: {"role": "assistant", "content": ""}`), zero or more content or tool-call deltas, one finish chunk (the only non-null `finish_reason`; its `delta` is `{}` or carries `citations`), an optional usage chunk (`choices: []` plus `usage`, only with `stream_options.include_usage`), then `[DONE]`. A failure after the stream has started is one in-band frame carrying the `Error` envelope, followed by `[DONE]`, with no finish or usage chunk; nothing is metered. Its `code` is `rate_limit_exceeded` (`rate_limit_error`), `upstream_unavailable` (`server_error`), or `null` with `type` `server_error` (retry) or `invalid_request_error` (`The model provider rejected the request.`: do not retry, change the request).'
                type: string
          description: The completion (`application/json`), or the event stream when `stream` is `true` (`text/event-stream`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                empty_content:
                  summary: An empty message
                  value:
                    error:
                      code: null
                      message: Message content must not be empty.
                      param: messages[0].content
                      type: invalid_request_error
                json_mode_with_tools:
                  summary: JSON mode combined with tools
                  value:
                    error:
                      code: null
                      message: response_format (JSON mode) cannot be combined with tools.
                      param: response_format
                      type: invalid_request_error
                no_default_model:
                  summary: "`model` omitted with no account default"
                  value:
                    error:
                      code: null
                      message: Multiple models are available for this account; pass 'model' explicitly, or ask eCourtDate to set a default model for your account.
                      param: model
                      type: invalid_request_error
                tool_call_id:
                  summary: A `tool` message answering no preceding tool call
                  value:
                    error:
                      code: null
                      message: tool_call_id does not match a preceding tool call.
                      param: messages[2].tool_call_id
                      type: invalid_request_error
                tool_not_allowed:
                  summary: A tool outside the bot's allowlist
                  value:
                    error:
                      code: tool_not_allowed
                      message: Tool 'get_weather' is not allowed for this model.
                      param: tools[1]
                      type: invalid_request_error
                validation:
                  summary: A field failed validation
                  value:
                    error:
                      code: null
                      message: Input should be 'system', 'developer', 'user', 'assistant' or 'tool'
                      param: messages[0].role
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated. The `RateLimit-*` headers are absent only when the body is not valid JSON (rejected before authentication).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                model_not_found:
                  value:
                    error:
                      code: model_not_found
                      message: "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default."
                      param: model
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No enabled bot or alias with that name on your account (`model_not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 5242880 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request body exceeds 5 MiB (5,242,880 bytes). The check runs before authentication, on a routed request: for a body sent without `Content-Length` an unknown path or wrong method is reported first (404, 405)."
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                model_rate_limited:
                  summary: The model is rate limited
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: The model is currently rate limited. Please retry shortly.
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: A per-account limit is exhausted (requests per minute or the daily token quota), or the underlying language model is rate limited. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "503":
          content:
            application/json:
              example:
                error:
                  code: upstream_unavailable
                  message: The model provider is temporarily unavailable. Please retry shortly.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The underlying language model or the text embedding service failed or timed out. Nothing was stored or charged; wait `Retry-After` seconds and retry.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Create a chat completion
      tags:
        - Chat
      x-scope: chat
  /v1/conversations:
    post:
      description: |-
        Create an empty server-side conversation. Unlike chat completions, where you resend the whole history on every call, a conversation stores its messages on the server: post one user message at a time and the reply is generated against the full stored history and the bot's knowledge base.

        `model` binds the conversation to a bot (the canonical slug is stored and returned) and is validated now, so a bad name fails here rather than on the first message; omit it to follow your account's default bot, resolved on every message. `metadata` holds up to 16 string key/value pairs of your own and is returned by create and retrieve. The body may be omitted entirely (no body, an empty body, or `null` is the same as `{}`).

        Errors: 400 for invalid `metadata` (shape, key count, key or value length, `param: metadata` or `metadata.<key>`) or an omitted `model` when the account has several bots and no default; 404 `model_not_found` for an unknown or disabled `model`.
      operationId: createConversation
      requestBody:
        content:
          application/json:
            examples:
              bound:
                summary: Bound to a bot, with metadata
                value:
                  metadata:
                    case_number: TR-2026-08841
                    session_id: web-7f3e2a
                  model: court-assistant
              default_bot:
                summary: Following the account default
                value: {}
            schema:
              $ref: "#/components/schemas/ConversationCreate"
      responses:
        "201":
          content:
            application/json:
              examples:
                conversation:
                  value:
                    created: 1787320991
                    id: c856e096-99bb-45c1-a0df-044dbbe0aeb7
                    metadata:
                      case_number: TR-2026-08841
                      session_id: web-7f3e2a
                    model: court-assistant
                    object: conversation
                    updated: 1787320991
              schema:
                $ref: "#/components/schemas/Conversation"
          description: The new conversation.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                metadata_count:
                  summary: More than 16 metadata keys
                  value:
                    error:
                      code: null
                      message: Dictionary should have at most 16 items after validation, not 17
                      param: metadata
                      type: invalid_request_error
                metadata_value:
                  summary: A metadata value over 512 characters
                  value:
                    error:
                      code: null
                      message: String should have at most 512 characters
                      param: metadata.notes
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated. The `RateLimit-*` headers are absent only when the body is not valid JSON (rejected before authentication).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                model_not_found:
                  value:
                    error:
                      code: model_not_found
                      message: "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default."
                      param: model
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No enabled bot or alias with that name on your account (`model_not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 5242880 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request body exceeds 5 MiB (5,242,880 bytes). The check runs before authentication, on a routed request: for a body sent without `Content-Length` an unknown path or wrong method is reported first (404, 405)."
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Create a conversation
      tags:
        - Conversations
      x-scope: chat
  /v1/conversations/{conversationId}:
    delete:
      description: |-
        Delete a conversation and its transcript. The response is 204 with no body. Deletion is permanent; a second delete of the same id is 404.

        Errors: 404 `not_found` when no conversation with that id exists on your account.
      operationId: deleteConversation
      parameters:
        - description: The conversation id.
          example: c856e096-99bb-45c1-a0df-044dbbe0aeb7
          in: path
          name: conversationId
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Deleted. No body.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Conversation not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No conversation with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Delete a conversation
      tags:
        - Conversations
      x-scope: chat
    get:
      description: |-
        Retrieve a conversation with its complete stored transcript, in order, alternating `user` and `assistant` messages. Assistant messages carry the `citations` they were answered with. The transcript is unbounded: every stored turn is returned, even though the model sees only up to the 40 most recent messages when answering. A conversation bound to a bot that was later disabled is still retrievable.

        Errors: 404 `not_found` when no conversation with that id exists on your account (another account's id behaves the same).
      operationId: retrieveConversation
      parameters:
        - description: The conversation id.
          example: c856e096-99bb-45c1-a0df-044dbbe0aeb7
          in: path
          name: conversationId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                conversation:
                  value:
                    created: 1787320991
                    id: c856e096-99bb-45c1-a0df-044dbbe0aeb7
                    messages:
                      - citations: null
                        content: What time does jury duty start, and where do I report?
                        role: user
                      - citations:
                          - chunk_index: 0
                            document_id: 2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13
                            score: 0.91
                            source_filename: juror-handbook.pdf
                            source_index: 1
                            text_preview: Report to the jury assembly room on the second floor by 8:30 a.m. Bring your summons and a photo id.
                        content: Jury duty at the county courthouse begins at 8:30 a.m. Report to the jury assembly room on the second floor with your summons and a photo id. [Source 1]
                        role: assistant
                    metadata:
                      case_number: TR-2026-08841
                      session_id: web-7f3e2a
                    model: court-assistant
                    object: conversation
                    updated: 1787321022
              schema:
                $ref: "#/components/schemas/ConversationDetail"
          description: The conversation with its transcript.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Conversation not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No conversation with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Retrieve a conversation
      tags:
        - Conversations
      x-scope: chat
  /v1/conversations/{conversationId}/messages:
    post:
      description: |-
        Add a user message to a conversation and get the assistant reply. The bot (the conversation's bound bot, or your account's current default) answers against the stored history plus the new message, grounded in its knowledge base; the model sees up to the 40 most recent messages (the window always opens on a `user` turn, so it can hold 39). Your message and the reply are stored together, as one turn, after the reply is complete.

        Set `stream: true` for server-sent events: `text/event-stream` frames of `{"conversation_id", "delta": {"content"}}` followed by one `{"conversation_id", "message", "done": true}` frame and `data: [DONE]`. The turn is stored before the done frame is sent; a stream that ends in an error frame stores nothing.

        Errors: 400 for an empty or blank `content` or one over 100,000 characters, a non-boolean `stream`, or, for a conversation that follows the account default, an account that now has several bots and no default (`param: model`); 404 `not_found` for an unknown conversation and 404 `model_not_found` when the conversation's bot has been disabled since it was created; 429 when the per-minute request limit, the daily token quota, or the underlying language model is exhausted; 503 `upstream_unavailable` when the model or the embedding service fails. Usage counts toward your daily token quota.
      operationId: addConversationMessage
      parameters:
        - description: The conversation id.
          example: c856e096-99bb-45c1-a0df-044dbbe0aeb7
          in: path
          name: conversationId
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            examples:
              question:
                summary: A question
                value:
                  content: What time does jury duty start, and where do I report?
              streaming:
                summary: Streamed
                value:
                  content: What time does jury duty start, and where do I report?
                  stream: true
            schema:
              $ref: "#/components/schemas/ConversationMessageRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              examples:
                reply:
                  value:
                    conversation_id: c856e096-99bb-45c1-a0df-044dbbe0aeb7
                    message:
                      citations:
                        - chunk_index: 0
                          document_id: 2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13
                          score: 0.91
                          source_filename: juror-handbook.pdf
                          source_index: 1
                          text_preview: Report to the jury assembly room on the second floor by 8:30 a.m. Bring your summons and a photo id.
                      content: Jury duty at the county courthouse begins at 8:30 a.m. Report to the jury assembly room on the second floor with your summons and a photo id. [Source 1]
                      role: assistant
              schema:
                $ref: "#/components/schemas/ConversationMessageResponse"
            text/event-stream:
              examples:
                cited_reply:
                  summary: A full stream with citations
                  value: |+
                    data: {"conversation_id":"c856e096-99bb-45c1-a0df-044dbbe0aeb7","delta":{"content":"Jury duty at the county courthouse begins at 8:"}}

                    data: {"conversation_id":"c856e096-99bb-45c1-a0df-044dbbe0aeb7","delta":{"content":"30 a.m. Report to the jury assembly room on the second floor with your summons and a photo id. [Source 1]"}}

                    data: {"conversation_id":"c856e096-99bb-45c1-a0df-044dbbe0aeb7","message":{"role":"assistant","content":"Jury duty at the county courthouse begins at 8:30 a.m. Report to the jury assembly room on the second floor with your summons and a photo id. [Source 1]","citations":[{"source_index":1,"document_id":"2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13","source_filename":"juror-handbook.pdf","chunk_index":0,"text_preview":"Report to the jury assembly room on the second floor by 8:30 a.m. Bring your summons and a photo id.","score":0.91}]},"done":true}

                    data: [DONE]

                in_band_error:
                  summary: A failure after the stream started
                  value: |+
                    data: {"conversation_id":"c856e096-99bb-45c1-a0df-044dbbe0aeb7","delta":{"content":"Jury duty at the county courthouse"}}

                    data: {"error":{"message":"The model provider is temporarily unavailable. Please retry shortly.","type":"server_error","param":null,"code":"upstream_unavailable"}}

                    data: [DONE]

              schema:
                contentMediaType: text/event-stream
                contentSchema:
                  $ref: "#/components/schemas/ConversationMessageChunk"
                description: 'Server-sent events. Every frame is `data: `, one compact JSON object (the `ConversationMessageChunk` schema), and a blank line; the stream ends with `data: [DONE]`. In order: zero or more delta frames (`{"conversation_id", "delta": {"content"}}`), then one done frame (`{"conversation_id", "message", "done": true}`) whose `message` is the same object the non-streaming call returns, then `[DONE]`. There is no role frame and no usage frame. A failure after the stream has started is one in-band frame carrying the `Error` envelope, followed by `[DONE]`; it can be the very first frame (same `code`/`type` vocabulary as chat completions, including the `invalid_request_error` frame that must not be retried). The turn is stored before the done frame is sent, so the transcript is complete once you read `done: true`; a stream that ends in an error frame stores nothing.'
                type: string
          description: The assistant reply (`application/json`), or the event stream when `stream` is `true` (`text/event-stream`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                blank_content:
                  summary: A whitespace-only message
                  value:
                    error:
                      code: null
                      message: Message content must not be empty.
                      param: content
                      type: invalid_request_error
                empty_content:
                  summary: An empty message
                  value:
                    error:
                      code: null
                      message: String should have at least 1 character
                      param: content
                      type: invalid_request_error
                stream_not_boolean:
                  summary: "`stream` is not a boolean"
                  value:
                    error:
                      code: null
                      message: Input should be a valid boolean
                      param: stream
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated. The `RateLimit-*` headers are absent only when the body is not valid JSON (rejected before authentication).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                model_not_found:
                  summary: The conversation's bot was disabled
                  value:
                    error:
                      code: model_not_found
                      message: "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default."
                      param: model
                      type: invalid_request_error
                not_found:
                  summary: Unknown conversation
                  value:
                    error:
                      code: not_found
                      message: Conversation not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No conversation with that id on your account (`not_found`), or the conversation's bot has been disabled since it was created (`model_not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 5242880 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request body exceeds 5 MiB (5,242,880 bytes). The check runs before authentication, on a routed request: for a body sent without `Content-Length` an unknown path or wrong method is reported first (404, 405)."
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                model_rate_limited:
                  summary: The model is rate limited
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: The model is currently rate limited. Please retry shortly.
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: A per-account limit is exhausted (requests per minute or the daily token quota), or the underlying language model is rate limited. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "503":
          content:
            application/json:
              example:
                error:
                  code: upstream_unavailable
                  message: The model provider is temporarily unavailable. Please retry shortly.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The underlying language model or the text embedding service failed or timed out. Nothing was stored or charged; wait `Retry-After` seconds and retry.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Add a message
      tags:
        - Conversations
      x-scope: chat
  /v1/documents:
    get:
      description: |-
        List the documents in your knowledge base, newest first, with cursor pagination in the OpenAI list style. Pass the previous page's `last_id` as `after` until `has_more` is `false`. Filter by `namespace`, `status`, and `source_type`; filters combine.

        Errors: 400 for `limit` outside 1 to 1000, an empty `after` or `namespace`, an `after` id that is not one of your documents, or an unknown `status` or `source_type` value.
      operationId: listDocuments
      parameters:
        - description: Page size, 1 to 1000.
          example: 25
          in: query
          name: limit
          required: false
          schema:
            default: 100
            maximum: 1000
            minimum: 1
            type: integer
        - description: "Cursor: the `last_id` of the previous page; the page starts after that document. An id that is not a document on your account is a 400."
          example: 17136f01-972d-4956-868e-8159833072e4
          in: query
          name: after
          required: false
          schema:
            minLength: 1
            type:
              - string
              - "null"
        - description: Only documents in this namespace (exact, case-sensitive match).
          example: general
          in: query
          name: namespace
          required: false
          schema:
            minLength: 1
            type:
              - string
              - "null"
        - description: Only documents in this status.
          example: ready
          in: query
          name: status
          required: false
          schema:
            enum:
              - processing
              - ready
              - failed
              - null
            type:
              - string
              - "null"
        - description: Only uploaded files (`upload`) or crawled pages (`crawl`).
          example: upload
          in: query
          name: source_type
          required: false
          schema:
            enum:
              - upload
              - crawl
              - null
            type:
              - string
              - "null"
      responses:
        "200":
          content:
            application/json:
              examples:
                empty:
                  summary: No documents match
                  value:
                    data: []
                    first_id: null
                    has_more: false
                    last_id: null
                    object: list
                page:
                  value:
                    data:
                      - chunk_count: 0
                        created_at: 2026-08-21T14:03:11.214000Z
                        error: No readable text was found in this document. Scanned or image-only files must be run through OCR before upload.
                        filename: fee-schedule-scan.pdf
                        id: 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                        namespace: general
                        object: document
                        source_type: upload
                        source_url: null
                        status: failed
                        updated_at: 2026-08-21T14:03:40.102000Z
                      - chunk_count: 18
                        created_at: 2026-08-21T14:03:11.214000Z
                        error: null
                        filename: traffic-division-faq.pdf
                        id: 17136f01-972d-4956-868e-8159833072e4
                        namespace: general
                        object: document
                        source_type: upload
                        source_url: null
                        status: ready
                        updated_at: 2026-08-21T14:03:42.908000Z
                      - chunk_count: 42
                        created_at: 2026-08-20T09:15:00.000000Z
                        error: null
                        filename: https://courts.example.gov/jury/handbook
                        id: 2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13
                        namespace: general
                        object: document
                        source_type: crawl
                        source_url: https://courts.example.gov/jury/handbook
                        status: ready
                        updated_at: 2026-08-20T09:17:26.511000Z
                    first_id: 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                    has_more: true
                    last_id: 2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13
                    object: list
              schema:
                $ref: "#/components/schemas/DocumentList"
          description: A page of documents.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                cursor:
                  summary: Unknown `after` cursor
                  value:
                    error:
                      code: null
                      message: "Unknown document id in `after`: '4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10'."
                      param: after
                      type: invalid_request_error
                limit:
                  summary: "`limit` out of range"
                  value:
                    error:
                      code: null
                      message: Input should be less than or equal to 1000
                      param: limit
                      type: invalid_request_error
                namespace:
                  summary: An empty `namespace` filter
                  value:
                    error:
                      code: null
                      message: String should have at least 1 character
                      param: namespace
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: List documents
      tags:
        - Documents
      x-scope: ingest
  /v1/documents/{documentId}:
    delete:
      description: |-
        Delete a document and every passage indexed from it, so it stops appearing in answers and citations immediately. Deletion is permanent; a second delete of the same id is 404.

        Errors: 404 `not_found` when no document with that id exists on your account.
      operationId: deleteDocument
      parameters:
        - description: The document id.
          example: 17136f01-972d-4956-868e-8159833072e4
          in: path
          name: documentId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                deleted:
                  value:
                    chunks_deleted: 18
                    deleted: true
                    id: 17136f01-972d-4956-868e-8159833072e4
                    object: document.deleted
              schema:
                $ref: "#/components/schemas/DocumentDeleted"
          description: Deleted.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Document not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No document with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Delete a document
      tags:
        - Documents
      x-scope: ingest
    get:
      description: |-
        Retrieve one document: its namespace, origin, indexing status, chunk count, and, when indexing failed, the sanitized `error`.

        Errors: 404 `not_found` when no document with that id exists on your account.
      operationId: retrieveDocument
      parameters:
        - description: The document id.
          example: 17136f01-972d-4956-868e-8159833072e4
          in: path
          name: documentId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                crawled:
                  summary: A crawled page
                  value:
                    chunk_count: 42
                    created_at: 2026-08-20T09:15:00.000000Z
                    error: null
                    filename: https://courts.example.gov/jury/handbook
                    id: 2f6e1c0a-8d3b-4c57-9a41-5b2e8d7f0c13
                    namespace: general
                    object: document
                    source_type: crawl
                    source_url: https://courts.example.gov/jury/handbook
                    status: ready
                    updated_at: 2026-08-20T09:17:26.511000Z
                failed:
                  summary: A failed upload
                  value:
                    chunk_count: 0
                    created_at: 2026-08-21T14:03:11.214000Z
                    error: No readable text was found in this document. Scanned or image-only files must be run through OCR before upload.
                    filename: fee-schedule-scan.pdf
                    id: 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                    namespace: general
                    object: document
                    source_type: upload
                    source_url: null
                    status: failed
                    updated_at: 2026-08-21T14:03:40.102000Z
                ready:
                  summary: An indexed upload
                  value:
                    chunk_count: 18
                    created_at: 2026-08-21T14:03:11.214000Z
                    error: null
                    filename: traffic-division-faq.pdf
                    id: 17136f01-972d-4956-868e-8159833072e4
                    namespace: general
                    object: document
                    source_type: upload
                    source_url: null
                    status: ready
                    updated_at: 2026-08-21T14:03:42.908000Z
              schema:
                $ref: "#/components/schemas/Document"
          description: The document.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Document not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No document with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Retrieve a document
      tags:
        - Documents
      x-scope: ingest
  /v1/embeddings:
    post:
      description: |-
        Embed one string or up to 96 strings with the API's text embedding model, in the OpenAI embeddings shape.

        Every embedding has 1024 dimensions. `encoding_format` chooses arrays of numbers (`float`) or base64-encoded little-endian 32-bit floats (`base64`, the OpenAI SDK default). The `input_type` extension tells the model whether the text is a document to store (`search_document`, the default) or a query to match against stored documents (`search_query`). `model` is an echo-only label; the response repeats it (or `default`).

        Errors: 400 for an empty input, an item over 100,000 characters (`param: input[i]`), more than 96 items, a non-string item, or `dimensions` other than 1024; 429 when the per-minute request limit, the daily token quota, or the embedding service is exhausted; 503 `upstream_unavailable` when the embedding service fails. Input tokens count toward your daily token quota.
      operationId: createEmbeddings
      requestBody:
        content:
          application/json:
            examples:
              documents:
                summary: Two passages to store
                value:
                  input:
                    - "Traffic Division hours: Monday through Friday, 8:00 a.m. to 4:30 p.m."
                    - Jury duty begins at 8:30 a.m. in the jury assembly room.
                  input_type: search_document
              query:
                summary: A query, base64-encoded result
                value:
                  encoding_format: base64
                  input: When does jury duty start?
                  input_type: search_query
            schema:
              $ref: "#/components/schemas/EmbeddingRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              examples:
                default_float:
                  summary: "`encoding_format: float` (vectors abbreviated)"
                  value:
                    data:
                      - embedding:
                          - 0.0123
                          - -0.0456
                          - 0.0789
                          - 0.0011
                          - -0.0204
                          - 0.0372
                          - 0.0098
                          - -0.0156
                        index: 0
                        object: embedding
                      - embedding:
                          - -0.0156
                          - 0.0098
                          - 0.0372
                          - -0.0204
                          - 0.0011
                          - 0.0789
                          - -0.0456
                          - 0.0123
                        index: 1
                        object: embedding
                    model: default
                    object: list
                    usage:
                      prompt_tokens: 31
                      total_tokens: 31
                encoded_base64:
                  summary: "`encoding_format: base64` (string abbreviated)"
                  value:
                    data:
                      - embedding: AACAPwAAAAAAAIA/AAAAAA==
                        index: 0
                        object: embedding
                    model: default
                    object: list
                    usage:
                      prompt_tokens: 7
                      total_tokens: 7
              schema:
                $ref: "#/components/schemas/EmbeddingList"
          description: One embedding per input.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                dimensions:
                  summary: Unsupported dimensions
                  value:
                    error:
                      code: null
                      message: This model produces 1024-dimensional embeddings; the 'dimensions' parameter is not supported.
                      param: dimensions
                      type: invalid_request_error
                empty_item:
                  summary: An empty input item
                  value:
                    error:
                      code: null
                      message: input strings must not be empty.
                      param: input[1]
                      type: invalid_request_error
                too_many:
                  summary: More than 96 inputs
                  value:
                    error:
                      code: null
                      message: input may hold at most 96 strings.
                      param: input[96]
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated. The `RateLimit-*` headers are absent only when the body is not valid JSON (rejected before authentication).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 5242880 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request body exceeds 5 MiB (5,242,880 bytes). The check runs before authentication, on a routed request: for a body sent without `Content-Length` an unknown path or wrong method is reported first (404, 405)."
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                model_rate_limited:
                  summary: The model is rate limited
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: The model is currently rate limited. Please retry shortly.
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: A per-account limit is exhausted (requests per minute or the daily token quota), or the text embedding service is rate limited. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "503":
          content:
            application/json:
              example:
                error:
                  code: upstream_unavailable
                  message: The model provider is temporarily unavailable. Please retry shortly.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The text embedding service failed or timed out. Nothing was stored or charged; wait `Retry-After` seconds and retry.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Create embeddings
      tags:
        - Embeddings
      x-scope: chat
  /v1/ingest/crawl:
    post:
      description: |-
        Crawl a public website into the knowledge base. Starting from `seed_urls`, the crawler follows links on the allowed hosts up to `max_depth` and `max_pages`, honoring `robots.txt` by default, and adds each page with readable text as a `crawl` document under `namespace`. Pages the site answers with an HTTP error status (4xx or 5xx) are not indexed; they are listed in the job's `errors[]` as `HTTP <status>`. The response is immediate: poll `GET /v1/ingest/crawl/{crawlJobId}` (or receive the `crawl.completed` webhook) for the outcome.

        A crawl has a 10-minute budget, fetching and indexing included; exceeding it ends the job `failed` with a single `crawl` entry `Crawl timed out.` (pages indexed before then stay ready). Size `max_pages` and `rate_limit_rps` so the crawl can finish within it.

        Only public `http` and `https` hosts can be crawled: private, loopback, link-local, and metadata addresses are rejected at submission and again at fetch time.

        Errors: 400 for an invalid `namespace`, no seed URLs or more than 100, a seed that is not a public http(s) URL (`param: seed_urls[i]`), or `max_pages`, `max_depth`, or `rate_limit_rps` outside their ranges.
      operationId: startCrawl
      requestBody:
        content:
          application/json:
            examples:
              minimal:
                summary: Defaults (hosts derived from the seeds)
                value:
                  namespace: general
                  seed_urls:
                    - https://courts.example.gov/self-help
              site:
                summary: A court website
                value:
                  allowed_domains:
                    - courts.example.gov
                  max_depth: 3
                  max_pages: 200
                  namespace: general
                  rate_limit_rps: 2
                  respect_robots_txt: true
                  seed_urls:
                    - https://courts.example.gov/
            schema:
              $ref: "#/components/schemas/CrawlRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              examples:
                accepted:
                  value:
                    crawl_job_id: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
                    status: processing
              schema:
                $ref: "#/components/schemas/CrawlResponse"
          description: The crawl job was created and queued.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                max_pages:
                  summary: "`max_pages` out of range"
                  value:
                    error:
                      code: null
                      message: Input should be less than or equal to 500
                      param: max_pages
                      type: invalid_request_error
                namespace:
                  summary: Invalid namespace
                  value:
                    error:
                      code: null
                      message: String should match pattern '^[a-z0-9][a-z0-9_-]{0,63}$'
                      param: namespace
                      type: invalid_request_error
                seed_url:
                  summary: A seed that cannot be crawled
                  value:
                    error:
                      code: null
                      message: "Seed URL 'http://10.0.0.5/admin' is not allowed: Host '10.0.0.5' resolves to a disallowed address (10.0.0.5)."
                      param: seed_urls[0]
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated. The `RateLimit-*` headers are absent only when the body is not valid JSON (rejected before authentication).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 5242880 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request body exceeds 5 MiB (5,242,880 bytes). The check runs before authentication, on a routed request: for a body sent without `Content-Length` an unknown path or wrong method is reported first (404, 405)."
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Start a crawl
      tags:
        - Crawling
      x-scope: ingest
  /v1/ingest/crawl/{crawlJobId}:
    get:
      description: |-
        Retrieve the status of a crawl job started by `POST /v1/ingest/crawl`: live page counts while it is `processing` (updated after every page fetched and indexed), then a terminal `completed`, `completed_with_errors`, or `failed` with one `errors[]` entry per failed page (or a single `crawl` entry when the crawl itself failed or timed out). Poll with backoff, or receive the `crawl.completed` webhook.

        Errors: 404 `not_found` when no crawl job with that id exists on your account.
      operationId: retrieveCrawlJob
      parameters:
        - description: The crawl job id returned by `POST /v1/ingest/crawl`.
          example: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
          in: path
          name: crawlJobId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                completed_with_errors:
                  summary: Finished, one failed page
                  value:
                    crawl_job_id: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
                    errors:
                      - error: The page could not be retrieved or contained no readable text.
                        url: https://courts.example.gov/forms/fee-waiver
                    pages_crawled: 148
                    pages_indexed: 146
                    status: completed_with_errors
                processing:
                  summary: Still running
                  value:
                    crawl_job_id: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
                    errors: []
                    pages_crawled: 37
                    pages_indexed: 35
                    status: processing
              schema:
                $ref: "#/components/schemas/CrawlJobStatus"
          description: The crawl job.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Crawl job not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No crawl job with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Retrieve a crawl job
      tags:
        - Crawling
      x-scope: ingest
  /v1/ingest/files:
    post:
      description: |-
        Upload 1 to 20 files as `multipart/form-data` and start one ingest job that extracts their text, splits it into passages, embeds them, and adds them to the knowledge base under `namespace`. The response is immediate: poll `GET /v1/ingest/jobs/{jobId}` (or receive the `ingest.completed` webhook) for the outcome, and `GET /v1/documents/{documentId}` for each document's status.

        Validation is all-or-nothing: every part is checked (count, emptiness, size, filename, extension, content) before anything is stored, so a 400 never leaves part of a batch behind.

        Errors: 400 for an invalid `namespace`, more than 20 files, or a file that is empty, over 25 MiB, of a type not in the allowlist, whose content does not match its extension, that is not valid UTF-8 (text types), or whose filename is too long or invalid (`param: file[i]`); 413 `request_too_large` when the whole body exceeds 20 files of 25 MiB plus 1 MiB of multipart overhead.
      operationId: ingestFiles
      requestBody:
        content:
          multipart/form-data:
            examples:
              two_files:
                summary: Two files into the `general` namespace
                value:
                  file:
                    - <traffic-division-faq.pdf bytes>
                    - <fee-schedule-2026.docx bytes>
                  namespace: general
            schema:
              $ref: "#/components/schemas/IngestFilesRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              examples:
                accepted:
                  value:
                    document_ids:
                      - 17136f01-972d-4956-868e-8159833072e4
                      - 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                    job_id: bfcde661-9eec-4142-a790-ca9a62c8e0f8
                    status: processing
              schema:
                $ref: "#/components/schemas/IngestFilesResponse"
          description: The ingest job was created and queued.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "400":
          content:
            application/json:
              examples:
                file_size:
                  summary: A file over 25 MiB
                  value:
                    error:
                      code: null
                      message: File exceeds the 26214400 byte limit.
                      param: file[0]
                      type: invalid_request_error
                file_type:
                  summary: A file type outside the allowlist
                  value:
                    error:
                      code: null
                      message: "File type '.exe' is not allowed. Allowed: .csv, .docx, .eml, .htm, .html, .md, .pdf, .txt, .xlsx."
                      param: file[1]
                      type: invalid_request_error
                namespace:
                  summary: Invalid namespace
                  value:
                    error:
                      code: null
                      message: namespace must match ^[a-z0-9][a-z0-9_-]{0,63}$ (lowercase letters, digits, '_' and '-', 1 to 64 characters).
                      param: namespace
                      type: invalid_request_error
                too_many_files:
                  summary: More than 20 files
                  value:
                    error:
                      code: null
                      message: "Too many files: 21 (max 20)."
                      param: file
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Invalid request. `param` names the first failing field (`null` when the body is not a JSON object); `code` is `null` unless stated.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "413":
          content:
            application/json:
              example:
                error:
                  code: request_too_large
                  message: Request body exceeds the 525336576 byte limit.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The body exceeds 20 files of 25 MiB plus 1 MiB of multipart overhead (525,336,576 bytes). The check runs before authentication, on a routed request.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Upload files
      tags:
        - Ingestion
      x-scope: ingest
  /v1/ingest/jobs/{jobId}:
    get:
      description: |-
        Retrieve the status of an ingest job started by `POST /v1/ingest/files`: live document counts while it is `processing` (updated after every document; transient failures are retried automatically), then a terminal `completed`, `completed_with_errors`, or `failed` with one `errors[]` entry per failed document. Poll with backoff, or receive the `ingest.completed` webhook.

        Errors: 404 `not_found` when no ingest job with that id exists on your account.
      operationId: retrieveIngestJob
      parameters:
        - description: The ingest job id returned by `POST /v1/ingest/files`.
          example: bfcde661-9eec-4142-a790-ca9a62c8e0f8
          in: path
          name: jobId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                completed_with_errors:
                  summary: Finished, one failure
                  value:
                    completed_documents: 2
                    errors:
                      - document_id: 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                        error: No readable text was found in this document. Scanned or image-only files must be run through OCR before upload.
                    failed_documents: 1
                    job_id: bfcde661-9eec-4142-a790-ca9a62c8e0f8
                    status: completed_with_errors
                    total_documents: 3
                processing:
                  summary: Still running
                  value:
                    completed_documents: 1
                    errors: []
                    failed_documents: 0
                    job_id: bfcde661-9eec-4142-a790-ca9a62c8e0f8
                    status: processing
                    total_documents: 3
              schema:
                $ref: "#/components/schemas/JobStatus"
          description: The job.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'ingest' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `ingest` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                not_found:
                  value:
                    error:
                      code: not_found
                      message: Job not found.
                      param: null
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No ingest job with that id on your account (`not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Retrieve an ingest job
      tags:
        - Ingestion
      x-scope: ingest
  /v1/models:
    get:
      description: "List the names your key can send as `model`: every enabled bot on your account, then every alias whose target is enabled, then `default` whenever the account default resolves (a configured default bot, a single enabled bot, or the built-in assistant when no bot is enabled; `default` is absent only when several bots are enabled and none is the default). The list is complete (no pagination) and is read through the same cache as `GET /v1/models/{modelId}` and chat, so a name listed here resolves on the next chat completion."
      operationId: listModels
      responses:
        "200":
          content:
            application/json:
              examples:
                models:
                  value:
                    data:
                      - created: 1784822400
                        id: court-assistant
                        object: model
                        owned_by: maple-county-courts
                      - created: 1785427200
                        id: jury-helpdesk
                        object: model
                        owned_by: maple-county-courts
                      - created: 1784822400
                        id: default
                        object: model
                        owned_by: maple-county-courts
                    object: list
              schema:
                $ref: "#/components/schemas/ModelList"
          description: The models your key can use.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: List models
      tags:
        - Models
      x-scope: chat
  /v1/models/{modelId}:
    get:
      description: "Retrieve one entry of `GET /v1/models` by its id: a bot slug, an alias, or `default`. The lookup is case-sensitive. Anything the list does not show (a disabled bot, an alias of a disabled bot, another account's bot, or an unknown name) is 404 `model_not_found`, with the available names in the message."
      operationId: retrieveModel
      parameters:
        - description: A bot slug, an alias, or `default`.
          example: court-assistant
          in: path
          name: modelId
          required: true
          schema:
            type: string
      responses:
        "200":
          content:
            application/json:
              examples:
                model:
                  value:
                    created: 1784822400
                    id: court-assistant
                    object: model
                    owned_by: maple-county-courts
              schema:
                $ref: "#/components/schemas/Model"
          description: The model.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "401":
          content:
            application/json:
              example:
                error:
                  code: invalid_api_key
                  message: Incorrect API key provided.
                  param: null
                  type: authentication_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "The request did not authenticate: the API key is missing, malformed, unknown, expired, or revoked."
          headers:
            WWW-Authenticate:
              $ref: "#/components/headers/WWW-Authenticate"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "403":
          content:
            application/json:
              example:
                error:
                  code: insufficient_scope
                  message: This API key does not have the 'chat' scope.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: The key lacks the `chat` scope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "404":
          content:
            application/json:
              examples:
                model_not_found:
                  value:
                    error:
                      code: model_not_found
                      message: "Model 'traffic-bot' was not found. Available models: court-assistant, jury-helpdesk. Omit the 'model' field to use this account's default."
                      param: model
                      type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: No enabled bot or alias with that name on your account (`model_not_found`).
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "429":
          content:
            application/json:
              examples:
                daily_quota:
                  summary: Daily token quota
                  value:
                    error:
                      code: insufficient_quota
                      message: "Daily token quota exceeded: 200000 tokens per day."
                      param: null
                      type: rate_limit_error
                requests_per_minute:
                  summary: Per-minute request limit
                  value:
                    error:
                      code: rate_limit_exceeded
                      message: "Rate limit exceeded: 60 requests per minute."
                      param: null
                      type: rate_limit_error
              schema:
                $ref: "#/components/schemas/Error"
          description: "A per-account limit is exhausted: requests per minute or the daily token quota. Wait `Retry-After` seconds. The fail-closed `Rate limiter unavailable.` variant carries `Retry-After` but no `RateLimit-*` headers (nothing was computed)."
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            Retry-After:
              $ref: "#/components/headers/Retry-After"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        "500":
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Internal server error.
                  param: null
                  type: server_error
              schema:
                $ref: "#/components/schemas/Error"
          description: An unexpected failure. Retry with backoff, then contact support with the `X-Request-ID`.
          headers:
            RateLimit-Limit:
              $ref: "#/components/headers/RateLimit-Limit"
            RateLimit-Remaining:
              $ref: "#/components/headers/RateLimit-Remaining"
            RateLimit-Reset:
              $ref: "#/components/headers/RateLimit-Reset"
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
        default:
          content:
            application/json:
              example:
                error:
                  code: null
                  message: Method not allowed.
                  param: null
                  type: invalid_request_error
              schema:
                $ref: "#/components/schemas/Error"
          description: Any other error (for example 405 with an `Allow` header), in the same envelope.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/X-Request-ID"
      summary: Retrieve a model
      tags:
        - Models
      x-scope: chat
security:
  - bearerAuth: []
servers:
  - description: Production
    url: https://api.chatbots.ecourtdate.com
tags:
  - description: OpenAI-compatible chat completions, answered by a bot from its knowledge base with `[Source N]` citations. Supports streaming, tools, and structured output.
    name: Chat
  - description: "The names your key can send as `model`: your account's bots, their aliases, and `default`. OpenAI-compatible."
    name: Models
  - description: Text embeddings (1024 dimensions) in the OpenAI shape, with an `input_type` extension for query versus document text.
    name: Embeddings
  - description: "Server-side conversations with persistent history: create one, post a message at a time, and the bot answers against the stored transcript and its knowledge base."
    name: Conversations
  - description: "The documents in your knowledge base (uploaded files and crawled pages): list with cursor pagination and filters, retrieve, and delete."
    name: Documents
  - description: Upload files into the knowledge base as a background job, poll the job for its outcome, or receive the `ingest.completed` webhook.
    name: Ingestion
  - description: Crawl a public website into the knowledge base as a background job, poll the job for its outcome, or receive the `crawl.completed` webhook.
    name: Crawling
  - description: Unauthenticated liveness check for monitoring.
    name: Status
webhooks:
  crawl.completed:
    post:
      description: "Sent when a crawl job started by `POST /v1/ingest/crawl` reaches `completed`, `completed_with_errors`, or `failed` (a crawl that times out ends `failed` with a single `crawl` error and still sends this event). The body mirrors `GET /v1/ingest/crawl/{crawlJobId}` plus `event` and `namespace`. Delivery: a `POST` with `Content-Type: application/json` to the webhook URL configured for your account, sent once per job when it reaches a terminal status. Respond with any `2xx` within 10 seconds; redirects are not followed. On any other outcome the delivery is retried twice more, 30 seconds and then 5 minutes later (3 attempts in total), with the same `X-ECD-Delivery-Id` and a fresh signature each time; after the third failure the event is dropped. Deduplicate on `X-ECD-Delivery-Id`, verify `X-ECD-Signature` before trusting the body, and treat the payload as a notification: the job status endpoint remains the source of truth."
      operationId: crawlCompleted
      parameters:
        - description: The event name, identical to the body's `event`.
          example: crawl.completed
          in: header
          name: X-ECD-Event
          required: true
          schema:
            enum:
              - crawl.completed
            type: string
        - description: A UUID identifying this event. Identical on every retry of the same event; use it to deduplicate.
          example: 0f6b1d2e-7c3a-4e9f-8b21-5d4c3a2b1e0f
          in: header
          name: X-ECD-Delivery-Id
          required: true
          schema:
            format: uuid
            type: string
        - description: "`t=<unix seconds>,v1=<hex>`: `v1` is the hexadecimal HMAC-SHA256, keyed with your account's webhook signing secret, of the string `<t>.<body>`, where `<body>` is the raw request body exactly as sent. `t` and `v1` are recomputed on every delivery attempt; reject a timestamp more than a few minutes from now to defeat replays. Present only when a signing secret is configured for your account."
          example: t=1787321022,v1=587f20b7dab0273ce3e1dd6318ce942e1bb146e1eb5deb479ac1eb420fc5cd4d
          in: header
          name: X-ECD-Signature
          required: false
          schema:
            pattern: ^t=[0-9]+,v1=[0-9a-f]{64}$
            type: string
      requestBody:
        content:
          application/json:
            examples:
              completed_with_errors:
                value:
                  crawl_job_id: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
                  errors:
                    - error: The page could not be retrieved or contained no readable text.
                      url: https://courts.example.gov/forms/fee-waiver
                  event: crawl.completed
                  namespace: general
                  pages_crawled: 148
                  pages_indexed: 146
                  status: completed_with_errors
              failed:
                summary: The crawl itself failed
                value:
                  crawl_job_id: 2d6f8c0e-5b1a-4f3c-9e7d-0a1b2c3d4e5f
                  errors:
                    - error: Crawl timed out.
                      url: crawl
                  event: crawl.completed
                  namespace: general
                  pages_crawled: 12
                  pages_indexed: 0
                  status: failed
            schema:
              $ref: "#/components/schemas/CrawlCompletedEvent"
        required: true
      responses:
        "200":
          description: Any `2xx` acknowledges the delivery. Anything else, a timeout, or a connection failure triggers a retry.
      security: []
      summary: Crawl job completed
      tags:
        - Crawling
  ingest.completed:
    post:
      description: "Sent when an ingest job started by `POST /v1/ingest/files` reaches `completed`, `completed_with_errors`, or `failed`. The body mirrors `GET /v1/ingest/jobs/{jobId}` plus `event` and `namespace`, so a handler usually needs no follow-up call. Delivery: a `POST` with `Content-Type: application/json` to the webhook URL configured for your account, sent once per job when it reaches a terminal status. Respond with any `2xx` within 10 seconds; redirects are not followed. On any other outcome the delivery is retried twice more, 30 seconds and then 5 minutes later (3 attempts in total), with the same `X-ECD-Delivery-Id` and a fresh signature each time; after the third failure the event is dropped. Deduplicate on `X-ECD-Delivery-Id`, verify `X-ECD-Signature` before trusting the body, and treat the payload as a notification: the job status endpoint remains the source of truth."
      operationId: ingestCompleted
      parameters:
        - description: The event name, identical to the body's `event`.
          example: ingest.completed
          in: header
          name: X-ECD-Event
          required: true
          schema:
            enum:
              - ingest.completed
            type: string
        - description: A UUID identifying this event. Identical on every retry of the same event; use it to deduplicate.
          example: 0f6b1d2e-7c3a-4e9f-8b21-5d4c3a2b1e0f
          in: header
          name: X-ECD-Delivery-Id
          required: true
          schema:
            format: uuid
            type: string
        - description: "`t=<unix seconds>,v1=<hex>`: `v1` is the hexadecimal HMAC-SHA256, keyed with your account's webhook signing secret, of the string `<t>.<body>`, where `<body>` is the raw request body exactly as sent. `t` and `v1` are recomputed on every delivery attempt; reject a timestamp more than a few minutes from now to defeat replays. Present only when a signing secret is configured for your account."
          example: t=1787321022,v1=d21e155da8e0a868e3b1940a7181ef1e8e8f95c4d5e85d0005335a416e3979eb
          in: header
          name: X-ECD-Signature
          required: false
          schema:
            pattern: ^t=[0-9]+,v1=[0-9a-f]{64}$
            type: string
      requestBody:
        content:
          application/json:
            examples:
              completed:
                value:
                  completed_documents: 2
                  errors: []
                  event: ingest.completed
                  failed_documents: 0
                  job_id: bfcde661-9eec-4142-a790-ca9a62c8e0f8
                  namespace: general
                  status: completed
                  total_documents: 2
              completed_with_errors:
                value:
                  completed_documents: 2
                  errors:
                    - document_id: 4b1f0c8e-2a77-4c0f-9d3e-6f2a1b9c7d10
                      error: No readable text was found in this document. Scanned or image-only files must be run through OCR before upload.
                  event: ingest.completed
                  failed_documents: 1
                  job_id: bfcde661-9eec-4142-a790-ca9a62c8e0f8
                  namespace: general
                  status: completed_with_errors
                  total_documents: 3
            schema:
              $ref: "#/components/schemas/IngestCompletedEvent"
        required: true
      responses:
        "200":
          description: Any `2xx` acknowledges the delivery. Anything else, a timeout, or a connection failure triggers a retry.
      security: []
      summary: Ingest job completed
      tags:
        - Ingestion
