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

# Observe thread observations

> Asynchronously observes one or more observations for this thread. Use the same endpoint for a single new observation or for bulk ingestion of existing thread history. Today every content block is text.

Ordering: observations are processed in per-thread FIFO order by the time Priors received them. Within a single request, the order of the observations array is preserved exactly. Two requests that are in flight concurrently to the same thread have undefined relative order — await the acknowledgement for one request before sending the next if strict ordering across requests matters. For bulk history, prefer a single request containing the ordered array, which is the only way to guarantee ordering deterministically.

This call is fire-and-forget: a 202 means the observations were accepted for processing, not that they are committed to memory. Poll the observation status endpoint to follow progress.




## OpenAPI

````yaml /openapi.yaml post /v1/agents/{agent_id}/threads/{thread_id}/observe
openapi: 3.0.3
info:
  title: Priors API
  description: >
    Priors gives you memory agents. An agent learns from the observations you
    send it and answers from what it learned.


    You call this API from your own backend with a partner API key. The key
    identifies your organization, which owns every agent, thread, observation,
    source, and memory it creates. Another organization receives 404 for those
    identifiers. Send `X-SP-End-User` alongside the key to act for one named
    person on your side.
  version: 0.1.0
servers:
  - url: https://app.sapientpriors.com/api
    description: Production
security:
  - partnerKey: []
tags:
  - name: Agents
    description: Create, read, update, and delete memory agents.
  - name: Threads
    description: 'Conversations under an agent: create them, teach them, and ask them.'
  - name: Sources
    description: Files an agent learned from, and the citations that point back to them.
  - name: Authentication
    description: What your key resolves to, and the limits that apply to it.
  - name: Billing
    description: Your organization's credits and charges.
paths:
  /v1/agents/{agent_id}/threads/{thread_id}/observe:
    post:
      tags:
        - Threads
      summary: Observe thread observations
      description: >
        Asynchronously observes one or more observations for this thread. Use
        the same endpoint for a single new observation or for bulk ingestion of
        existing thread history. Today every content block is text.


        Ordering: observations are processed in per-thread FIFO order by the
        time Priors received them. Within a single request, the order of the
        observations array is preserved exactly. Two requests that are in flight
        concurrently to the same thread have undefined relative order — await
        the acknowledgement for one request before sending the next if strict
        ordering across requests matters. For bulk history, prefer a single
        request containing the ordered array, which is the only way to guarantee
        ordering deterministically.


        This call is fire-and-forget: a 202 means the observations were accepted
        for processing, not that they are committed to memory. Poll the
        observation status endpoint to follow progress.
      operationId: observe_thread
      parameters:
        - $ref: '#/components/parameters/AgentId'
        - $ref: '#/components/parameters/ThreadId'
        - $ref: '#/components/parameters/EndUser'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ObserveRequest'
            examples:
              observe_history:
                summary: Ingest prior thread history
                value:
                  observations:
                    - content:
                        - type: text
                          text: >-
                            The service keeps restarting after I build the
                            image.
                      timestamp: '2026-07-16T12:02:00Z'
                      tags:
                        - user
                    - content:
                        - type: text
                          text: docker compose ps shows api exited with code 1.
                      timestamp: '2026-07-16T12:03:00Z'
                      tags:
                        - environment
                        - terminal
      responses:
        '202':
          description: Observation request accepted for asynchronous processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ObserveAcceptedResponse'
              examples:
                accepted:
                  value:
                    agent_id: agent_01JZ8X9K6AS3V8SGP0Q9A2P4FH
                    thread_id: thread_01JZ8XASKN4R1HXV4X19A3T02J
                    observation_id: obs_01JZ8XQ8JQZNQDRG6EB7N1M3PV
                    accepted: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  parameters:
    AgentId:
      name: agent_id
      in: path
      required: true
      description: Unique managed memory-agent identifier.
      schema:
        type: string
        maxLength: 32
        pattern: ^agent_[0-9A-HJKMNP-TV-Z]{26}$
    ThreadId:
      name: thread_id
      in: path
      required: true
      description: Unique thread identifier scoped to the agent.
      schema:
        type: string
        maxLength: 33
        pattern: ^thread_[0-9A-HJKMNP-TV-Z]{26}$
    EndUser:
      name: X-SP-End-User
      in: header
      required: false
      description: >
        Your own identifier for the person this request acts for: an account
        number, a row id, anything stable on your side. It works only with a
        partner API key. The value is not stored. Priors hashes it together with
        your organization id, so two clients that send the same string are still
        two different people.


        With the header, you reach the threads that same person created and no
        others. A new thread is recorded against that person, and `GET
        /v1/agents/{agent_id}` returns that person's conversations only. Without
        the header, the key acts as the organization and reaches every thread on
        every agent it owns. Learned memory is shared across the agent either
        way. Only the thread list narrows.


        Sent with anything that is not a partner API key, the header returns 400
        instead of being ignored. Sent empty or as whitespace, it also returns
        400, and it is never read as absent: absent means the whole
        organization, which is wider access than you asked for. Surrounding
        whitespace is removed. The 255-character limit is measured on the value
        as sent.
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: \S
  schemas:
    ObserveRequest:
      type: object
      required:
        - observations
      properties:
        observations:
          $ref: '#/components/schemas/Observations'
      additionalProperties: false
    ObserveAcceptedResponse:
      type: object
      required:
        - agent_id
        - thread_id
        - observation_id
        - accepted
      properties:
        agent_id:
          $ref: '#/components/schemas/AgentId'
        thread_id:
          $ref: '#/components/schemas/ThreadId'
        observation_id:
          $ref: '#/components/schemas/ObservationId'
          description: >
            Server-issued identifier for the asynchronous observation request.
            Pass it to the observation status endpoint to follow progress.
        accepted:
          type: boolean
      additionalProperties: false
    Observations:
      type: array
      minItems: 1
      maxItems: 100
      description: >
        One or more observations. A single item represents real-time
        observation; multiple observations represent history or bulk ingestion.
        Array order is preserved exactly and is the only deterministic way to
        order a batch.
      items:
        $ref: '#/components/schemas/ObservationItem'
    AgentId:
      type: string
      pattern: ^agent_[0-9A-HJKMNP-TV-Z]{26}$
      description: Server-generated agent identifier.
    ThreadId:
      type: string
      pattern: ^thread_[0-9A-HJKMNP-TV-Z]{26}$
      description: Server-generated thread identifier.
    ObservationId:
      type: string
      pattern: ^obs_[0-9A-HJKMNP-TV-Z]{26}$
      description: Server-generated observation identifier.
    ErrorResponse:
      type: object
      required:
        - error
        - code
        - request_id
      properties:
        error:
          type: string
        code:
          type: string
          description: >
            Machine-readable condition. Branch on this rather than on the status
            where the two differ. All three of these can arrive as a 5xx and
            they want different handling: `service_unavailable` (503) is a
            dependency outage worth retrying, `service_not_configured` (503) is
            the same transport failure that retrying will NEVER clear because a
            feature is not switched on for your organization, and
            `internal_error` (500) is a defect that will fail the same way
            again. Known values: invalid_request, unauthorized, forbidden,
            payment_required, not_found, agent_not_found, thread_not_found,
            conflict, agent_quota_exceeded, duplicate_upload_in_progress,
            document_already_ingested, document_uploaded_elsewhere,
            agent_not_ready, payload_too_large, rate_limit_exceeded,
            service_unavailable, service_not_configured,
            answer_model_unavailable, internal_error, and the document codes an
            upload can return (unsupported_document, document_needs_ocr,
            encrypted_document, malformed_document, empty_document,
            document_too_large, document_too_complex, document_too_many_turns,
            document_too_many_pages, document_too_large_for_ocr,
            document_pages_unexpected, document_service_unavailable, ocr_busy,
            ocr_failed, ingestion_failed).


            answer_model_unavailable is a narrower `service_unavailable` (503):
            the model that writes the visible reply could not be reached after
            retries. It is worth its own code because it is the one dependency
            whose outage a caller may want to word differently -- the turn
            produced no answer at all, and retrying shortly is the right advice.


            agent_not_found and thread_not_found are narrower `not_found`s, sent
            so a client can tell WHICH resource vanished and recover accordingly
            without matching on the English message. Treat any unrecognised
            value as the status code's default meaning rather than rejecting the
            response.
        request_id:
          type: string
          description: >
            Server-issued identifier for this request. Include it when reporting
            a problem so the call can be correlated with server logs.
        details:
          type: string
          description: |
            Present on a 429. Names which control refused the request:
              `user`                    Your per-subject window, counted against `X-SP-End-User`.
              `organization`            Your organization's ceiling, across every key you hold.
              `capacity`                The service is at capacity. Retry.
              `ingestion-organization`  Too much of your own document work is already running.
              `ingestion-capacity`      As above, service-wide.
      additionalProperties: false
    ObservationItem:
      type: object
      required:
        - content
      properties:
        content:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/ContentBlock'
        timestamp:
          type: string
          format: date-time
          description: >
            When the observation occurred, used as the memory's temporal anchor.
            This does NOT drive processing order — ordering is by server receive
            time (see the observe endpoint). Defaults to the server receive time
            if omitted.
        speaker:
          type: string
          minLength: 1
          maxLength: 200
          description: >
            Who wrote or said this observation, for example a person's name.
            Send it whenever you know it. Use a readable name, not an id: the
            agent reads it when it reasons about the conversation. In a thread
            that several people share, it is what keeps their messages apart.
            Without `speaker`, Priors reads the first tag as the speaker (older
            behavior, kept for existing clients), and records `user` when there
            are no tags either.
        tags:
          type: array
          description: >
            Free-form labels for the source, channel, or topic, for example
            `email` or `whatsapp`. When you send `speaker`, the agent keeps
            every tag with the turn and sees them with it. Without `speaker`,
            the first tag is read as the speaker instead, and the tags are not
            kept.
          maxItems: 20
          items:
            type: string
            minLength: 1
            maxLength: 64
        img_url:
          type: string
          maxLength: 2048
          description: >
            Optional image URL for this observation. Priors fetches it,
            describes the image, and learns the description together with the
            text. The URL must be reachable from the internet. If the fetch
            fails, the observation is still learned from its text and caption.
        blip_caption:
          type: string
          maxLength: 2000
          description: >
            Optional precomputed image caption, folded into the turn text
            alongside (or instead of) the fetched description.
        source_ids:
          type: array
          maxItems: 512
          items:
            type: string
            pattern: ^(?:img|src)_[0-9a-f]{32}$
          description: >
            Stored uploads this observation came from, inherited by every memory
            generated from it so a citation can resolve back to the file. An
            upload sets these itself: the document's own source_id, plus one per
            figure whose description landed in this chunk.
      additionalProperties: false
    ContentBlock:
      oneOf:
        - $ref: '#/components/schemas/TextContent'
      discriminator:
        propertyName: type
        mapping:
          text: '#/components/schemas/TextContent'
    TextContent:
      type: object
      required:
        - type
        - text
      properties:
        type:
          type: string
          enum:
            - text
        text:
          type: string
          minLength: 1
          maxLength: 100000
      additionalProperties: false
  responses:
    BadRequest:
      description: >
        The request is not one this route can act on, and sending it again
        unchanged does not help. Usually the payload: a field missing, or a
        value out of range.


        It is also the credential and the headers around it, and each of these
        is refused rather than ignored: `X-SP-End-User` sent empty, or longer
        than 255 characters; and a key on a request that carries
        `Sec-Fetch-Site`, which only a browser sets.


        `code` is `invalid_request` throughout. `error` says which of these it
        was.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: |
        The credential is valid but may not take this action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: >
        No such id, or it is not one this key reaches. It is never a 403, so a
        key cannot be used to learn which ids exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: >
        A rate window or an admission control refused the request. `Retry-After`
        carries the seconds to wait, and `details` names which control refused.
        Nothing was learned and nothing was charged.
      headers:
        Retry-After:
          description: Seconds to wait before retrying. Always at least 1.
          required: true
          schema:
            type: integer
            minimum: 1
        X-RateLimit-Limit:
          description: Requests the refusing window admits in its period.
          schema:
            type: integer
            minimum: 1
        X-RateLimit-Remaining:
          description: Requests left in the current window. Zero on a refusal.
          schema:
            type: integer
            minimum: 0
        X-RateLimit-Reset:
          description: Unix timestamp (seconds) at which the window admits again.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    partnerKey:
      type: http
      scheme: bearer
      description: >
        Your API key, shaped `sp_<key_id>_<secret>`, sent as `Authorization:
        Bearer sp_...`. It identifies your organization, not a person. Your
        organization owns the agents, holds the credit, and is billed. Every key
        your organization holds reaches the same things, so you rotate a key by
        minting the new one, deploying it, and revoking the old one.


        Keep the key on your server. A request that carries a `Sec-Fetch-Site`
        header is refused with 400, because only a browser sets that header.
        Priors sends no CORS headers, so a browser blocks the call before it
        leaves the page. The secret is shown once when the key is minted and is
        stored only as a hash. A lost secret is replaced, never recovered.


        An administrator of your organization mints and revokes keys in the
        Priors application, under Settings. There is no endpoint for it: a key
        reaches your whole organization, so issuing one is an act of
        administration.

````

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