---
title: "Expose Schemas"
method: GET
path: "/api/v1/chat/schemas"
tags: ["chat"]
---

# Expose Schemas

`GET /api/v1/chat/schemas`

Schema exposure endpoint for OpenAPI/TypeScript generation.
This endpoint is never called by the frontend but ensures all chat-related
schemas are included in the OpenAPI specification for type generation.

## Response `200`

Successful Response

- SchemaExposure — Schema exposure model for all chat-related types. This endpoint exists solely to expose schemas to OpenAPI/TypeScript generation. These fields are never actually populated in responses.
  - `enrichment_agent_response` ToolResourceResponse — Generic response model for tool/agent outputs. Structured response that includes a message and any resource references created or modified by the tool.
    - `message` string, required — Response to prompt and description of the changes made.
    - `resources` ResourceReference[] — List of resources created or modified by the tool.
      - `resource_type` 'enrichment' | 'layer' | 'view', required — Enum for different resource types that can be referenced in chat.
      - `operation` 'create' | 'update' | 'delete', required — Enum for operations that can be performed on resources.
      - `resource_id` string, required — The ID of the resource
      - `display_name` string, required — Display name for the resource
      - `created_at` string, nullable — ISO timestamp when resource was created in this chat
      - `feature_count` integer, nullable — Number of features in the created resource
      - `task_id` string, nullable — Celery task ID for async operations like deep research
  - `not_enough_context` NotEnoughContext
    - `reason` string, required — The reason why the action is not possible
  - `create_layer_parameters_response` CreateLayerSuccess — Successful layer creation result with copy parameters.
    - `message` string, required — Success message explaining the recommended layer configuration
    - `copy_parameters` CopyLayerParameters, required — Parameters for creating a layer from an Overture/core data table. Defines the source table, column selection, and filters used by the layer creation pipeline. target_layer_id and reference_layer_id are provided separately.
      - `source_table_name` string, required — Name of the source Overture table (e.g., 'building', 'place')
      - `columns_to_copy` string[], nullable — List of column names to copy (if None, copies all non-geometry columns)
      - `column_filters` object, nullable — Dictionary of column_name: value filters for data filtering
    - `layer_description` string, required — One-sentence description of what this layer contains and its purpose
    - `notes` string, nullable — Any caveats or limitations that should be considered when using this layer. Include information about the strategy taken to create the layer and any issues that had occured. Limit to 100 words. Omit if there are no issues.
  - `enrichment_value_base_model` EnrichmentValueBaseModel — Enrichment value domain model.
    - `reasoning` string, nullable — 1-2 sentence explanation of the enrichment value.
    - `value` union — The enrichment value.
      - string
      - number
      - boolean
      - string[]
      - object[]
      - object
      - integer
    - `citations` EnrichmentCitation[], nullable — List of citations from web search sources that support the enrichment value if any were used.
      - `url` string, nullable — Optional URL of the source that supports the enrichment value.
      - `title` string, nullable — Optional human-readable title for the source.
      - `snippet` string, nullable — Optional snippet or excerpt from the cited material.
      - `provider` string, nullable — Provider or domain the citation originated from.
      - `source_type` string, nullable — Provider-specific type identifier for the cited source.
    - `research_outcome` 'completed' | 'insufficient_inputs' | 'sources_blocked' | 'sources_failed_transient' — Why a null final is null. Only ``SOURCES_FAILED_TRANSIENT`` is retryable. Shared vocabulary between the producer's structured self-report (``ChatResearchOutcomeMixin``) and the consumer-side classifier (``core/services/enrichment_research_outcome.py``), so the two stay directly comparable. Lives here rather than in the service module because ``core/models`` is an import-linter leaf and must not import ``core/services``.
    - `state` 'unenriched' | 'enriched' | 'attempted' | 'user_edited' | 'pending' — Enum representing the state of an enrichment value.
    - `created_at` string, date-time, nullable
  - `enrichment_status_changed_event` EnrichmentStatusChangedEvent — Fired when one enrichment workflow reaches a terminal state. Unlike ``CreditStateChangedEvent`` this is payload-rich. It carries: - ``workflow_id`` — the client registry's key; routes the event to the cell/column it belongs to. - ``project_id`` / ``enrichment_id`` / ``feature_id`` — the coordinates, so an event for a workflow the client didn't dispatch in this session (e.g. after a reload, before reconcile) can still build a registry entry from the event alone. - ``value`` — on SUCCESS, the persisted ``EnrichRowResponse``-shaped dict (``{value, reasoning, citations, state}``) built from the SAME ``db_value`` written to the sandbox, so the live cell is byte-identical to what a refresh re-reads. ``None`` on FAILURE (no value was written). The terminal-status policy lives on the server (the workflow's own terminal state), not on the client — so the client can no longer drift a transient signal into a false terminal (the bug class this replaces).
    - `type` 'enrichment_status_changed'
    - `workflow_id` string, required
    - `project_id` string, uuid, required
    - `enrichment_id` string, uuid, required
    - `feature_id` string, required
    - `status` 'SUCCESS' | 'FAILURE', required
    - `value` object, nullable
  - `view_state_changed_event` ViewStateChangedEvent — Fired after a project view mutation commits (create / update / filter / delete / column change), over the same per-user channel. Payload-rich + version-stamped, mirroring ``EnrichmentStatusChangedEvent``: the FE applies the carried view(s) straight to its React Query cache without a refetch round-trip, and uses each view's ``version`` to discard stale or out-of-order events. Carries only the *affected* view(s) plus ``deletedViewIds`` (not the full list) so the FE merges rather than overwrites siblings. Fields are camelCase so the SSE endpoint can forward ``model_dump_json`` verbatim and the FE reads the same keys it reads off ``/views``.
    - `type` 'view_state_changed'
    - `projectId` string, uuid, required
    - `views` object[], required
    - `deletedViewIds` string[]
  - `layer_state_changed_event` LayerStateChangedEvent — Fired after a layer metadata mutation commits (create / delete / rename / restyle / recolor), over the same per-user channel — the layer sibling of ``ViewStateChangedEvent``. Payload-rich + version-stamped: the FE applies the carried layer(s) straight to its React Query cache without a refetch round-trip, and uses each layer's ``metadata_version`` to discard stale or out-of-order events. Carries only the *affected* layer(s) plus ``deletedLayerIds`` so the FE merges rather than overwrites siblings. Unlike views, the nested layers serialize in their **snake_case** wire shape — ``LayerModel`` has no field aliases, and the FE reads the same snake_case keys off ``GET /projects/{id}/layers`` through ``layerMappers.fromApiModel``. So no ``field_serializer`` is needed here; the default dump is already the wire shape.
    - `type` 'layer_state_changed'
    - `projectId` string, uuid, required
    - `layers` LayerModel[]
      - `id` string, uuid, required
      - `project_id` string, uuid, required
      - `reference_layer_id` string, uuid, nullable
      - `reference_layer_ids` string[]
      - `name` string, required
      - `description` string, nullable
      - `style_config` object, required
      - `feature_count` integer, required
      - `total_size_bytes` integer, required
      - `data_version` integer
      - `schema_version` integer
      - `metadata_version` integer
      - `base_attributes` object, required
      - `feature_enrichments` EnrichmentModel[]
        - `id` string, uuid
        - `layer_id` string, uuid, nullable
        - `name` string
        - `description` string, nullable
        - `tool` string
        - `params` object
        - `dtype` string — Must be one of string, boolean, list[str], jsonb, int, float, categorical, url
        - `created_at` string, date-time, nullable
        - `updated_at` string, date-time, nullable
        - `credits_per_row` integer, required — Return the number of credits per row for this tool.
        - `is_configured` boolean, required — Check if the enrichment is configured with a valid tool. Returns: bool: True if tool is configured, False otherwise
        - `is_configuration_failed` boolean, required — Check if the enrichment's configuration failed. Returns: bool: True if configuration was attempted but failed
        - `is_data_source` boolean, required — Check if the enrichment is a data source. Returns: bool: True if the enrichment is a data source, False otherwise
      - `sandbox_relation_name` string, nullable
      - `render_mode` 'detail_always' | 'agg_at_low_zoom'
      - `display_kind` 'data' | 'boundary' — Whether the layer is a queryable dataset (``data``) or a styling-only overlay (``boundary``). Boundary layers paint on the map but suppress the per-layer table, column manager, and feature-detail panel — there is no per-row data worth surfacing.
      - `extent_bounds` ThumbnailBounds — Bounding box for project thumbnail map rendering. Represents the geographic extent of project features for generating static map thumbnails via Mapbox.
        - `min_lon` number, required
        - `min_lat` number, required
        - `max_lon` number, required
        - `max_lat` number, required
      - `has_drawable_geometry` boolean, nullable — Whether the layer holds geometry the map can draw. False marks a table-only upload; None means unrecorded — every layer created before this field, and every creation path that does not state it. Consumers must treat None as unknown, not as False.
      - `created_at` string, date-time, nullable, required
      - `updated_at` string, date-time, nullable, required
      - `columns` LayerColumn[], nullable — Producer-declared, resolved per-column metadata (id, key, provenance, display name, data type) — schema/lineage only. Visibility and ordering live on the view, seeded at register-layer time. None for legacy pre-cutover layers; the composer derives sensible defaults from physical introspection.
        - `id` string, required — Stable column identifier within a layer. Hashed from the layer id plus the column's kind-specific stable identity. Source and merged columns omit `key` so alias renames keep the same id; agent-derived columns include `key` to distinguish multiple computations anchored on the same source.
        - `key` string, required
        - `provenance` ColumnProvenance, required — Where a column came from and how to look up its canonical metadata. `source_column` is the canonical Overture column path (e.g. `names.primary`, not the agent's SQL alias `name`). The resolver uses it to look up the display label and to derive a rename-stable column id — making alias mismatches impossible by construction.
          - `kind` 'source' | 'merged' | 'enrichment' | 'system' | 'agent_derived', required — Provenance discriminator for a layer column. * `source` — column SELECTed directly from the canonical Overture table. * `merged` — column joined in from another canonical source table (e.g. `parcel_owner` on a building layer joined parcel data). * `enrichment` — populated by an enrichment runner, not the agent's SQL. (Reserved for the upcoming enrichment unification — not used yet.) * `system` — agent-facing hide-by-default escape for lineage columns merged for enrichment plumbing the user shouldn't see by default (e.g. parcel address fields plumbed onto a building layer for Owner Contact Info). Stays in the registered schema and can be surfaced later via `modify_columns`. * `agent_derived` — computed in the agent's SQL (`SUM(...) AS total`, `CASE WHEN ... END AS bucket`). Still requires source_table+source_column anchoring so view deltas survive a column rename.
          - `source_table` string, nullable — Canonical Overture table this column derives from. None for fully derived (composite) columns with no canonical lineage.
          - `source_column` string, nullable — Canonical column path on `source_table` (e.g. `names.primary`). Used by the resolver for display-name lookup and stable column ids — NOT the agent's SQL alias.
        - `display_name` string, required — Resolved user-facing column label. Unit-agnostic — does NOT include unit tokens like `(ft)`, `(m)`, `(m²)`, or `(acres)`. The FE composes the column header as `display_name + (unit)` where `unit` is the single source of truth for the unit suffix. See MAIA-1872.
        - `data_type` 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'geometry' | 'array' | 'object' — Optional frontend type hint for a layer column.
        - `unit` string, nullable — Display-unit suffix declared by the agent at register-layer time. Single source of truth for the user-visible unit on this column — the FE composes the header as `display_name + (unit)` and renders cells as `value + unit`. Round-trips through the resolver verbatim. Numeric formatting (decimal places) is picked from a unit→format lookup; unknown units fall back to integer formatting + raw suffix so the agent can declare arbitrary unit strings without code changes. Null for non-unit columns (names, IDs, categorical text). Currency uses the `currency` data_type / semantic type, NOT this field.
        - `semantic_type` 'identifier' | 'name' | 'address_part' | 'address_full' | 'currency' | 'area' | 'height' | 'count' | 'category' | 'zoning' | 'date' | 'phone' | 'website' | 'percent' | 'generic' — What a column *means* — drives FE rendering and formatting. Independent from ``ColumnDataType`` (the storage type hint): a column can be ``data_type=number, semantic_type=currency`` (parcel ``landval``) or ``data_type=number, semantic_type=area`` (``area_acres``). The FE renderer keys off ``semantic_type``, not ``data_type``. Lives here (with the wire model) rather than on ``LayerKind`` because under the self-describing contract this rides on ``LayerColumn`` across the wire — ``LayerKind`` is the backend-only producer that hydrates it.
        - `role` 'stat' | 'other' — Narrow detail-panel placement signal, orthogonal to ``semantic_type``. The one signal that cannot be derived from ``semantic_type``: parcel ``parval`` and ``improvval`` are both ``CURRENCY``, but only ``parval`` is a headline stat. ``role`` carries that; ``semantic_type`` carries content/format. Ordering and visibility live on the *view*, not here.
        - `description` string, nullable — Human-readable column description surfaced in the table column metadata. Hydrated server-side (canonical column metadata or the agent's declaration), not persisted as a separate wire bag.
      - `data_restricted` boolean — Share-response-only signal that the layer's geometry is shown while every per-row attribute is withheld (parcel layers on anonymous share links). Always False on authenticated reads; set True solely by the shared-project layer builder. The FE renders a 'data unavailable in the public version' state in place of an empty grid.
      - `viz_withheld_column_keys` string[] — Keys of columns the MVT tile refuses to emit as a classed-visualization column, computed per column by ``column_withheld_from_viz`` and stamped by ``to_authenticated_api_format``. The picker subtracts these so it offers exactly what the tile will paint; re-deriving the rule client-side from a whole-layer flag over-counts and hides ordinary columns. Empty on share responses, which render no picker.
      - `title_template` string[] — Ordered column keys the FE concatenates to compose a feature's title (e.g. `[parcelnumb]`, `[primary_address_full]`). Sourced from the layer's `LayerKind`, empty for permissive kinds — the FE keeps its generic title fallback for the long tail.
      - `address_template` string[] — Ordered column keys the FE concatenates to compose a feature's address line. Sourced from the layer's `LayerKind`; may reference keys that aren't typed canonical columns (city, state_abbr, …). Empty for permissive kinds.
    - `deletedLayerIds` string[]
  - `chat_summarization_completed_event` ChatSummarizationCompletedEvent — Fired when a chat's background ``summarize_workflow`` finishes persisting. Summarization runs as a fire-and-forget DBOS workflow after a turn crosses the context threshold; the turn's ``complete`` SSE frame (carrying ``summarization_task_id``) is the *enqueue* moment, ~50s before the summary actually generates + persists. The original turn's direct stream has closed by then, so this advisory event over the per-user fanout is how the client learns the summary is genuinely done and renders the "summarized" divider. Advisory only — the durable summary is the source of truth: on reload the FE reconstructs the divider from persisted history (``detectSummarization``), so a dropped publish degrades to "divider appears on next reload," never a stuck state. Carries only ``projectId`` (camelCase wire shape, matching ``ViewStateChangedEvent`` / ``LayerStateChangedEvent``): ``chats.project_id`` is UNIQUE (one chat per project), so the project anchors the chat and the FE needs no ``chat_id`` to route.
    - `type` 'chat_summarization_completed'
    - `projectId` string, uuid, required
  - `project_renamed_event` ProjectRenamedEvent — Fired when a project's name changes server-side, over the per-user channel. The trigger today is auto-title generation: a fire-and-forget task that generates a name several seconds into the turn, and can retry on a *later* message. By then the project is already ``ready`` and the FE's creating-project poller has stopped watching it, so without this event the new name only surfaces on the next projects refetch — a manual refresh in practice. Carries the coordinates the FE needs to patch the projects-list cache in place: ``projectId`` to locate the row, ``name`` for the display, and ``titleSource`` so the cached row matches what a refetch would read. camelCase wire shape, matching ``ViewStateChangedEvent`` / ``LayerStateChangedEvent``. Advisory only — a dropped publish degrades to the FE's reconnect-resync.
    - `type` 'project_renamed'
    - `projectId` string, uuid, required
    - `name` string, required
    - `titleSource` 'placeholder' | 'auto' | 'user', required — Provenance + lifecycle for the project's name field. Replaces an em-dash string heuristic (`" — " not in project.name`) that gated whether the sandbox auto-titler should rename a project from its skeleton name. Encoding the decision as a typed enum makes the check a single comparison and prevents future titlers that happen to produce em-dashes from silently disabling the branch. - PLACEHOLDER: title is initial scaffolding (sandbox skeleton's "Boulder County", initial scaffold name); eligible to be replaced by the next titler. - AUTO: title set by an LLM auto-titler (agent_service first-message rename or project setup using the user's plan name); final. - USER: title set by an explicit user rename via PUT /projects/{id}; final.
  - `map_fly_to_event` MapFlyToEvent — Fired when the agent moves the map camera to a resolved feature, over the per-user channel — the reply to a "zoom to <address/place/parcel>" request. Carries the envelope ``bbox`` (``[minLng, minLat, maxLng, maxLat]``) of a feature already in the project's data; the FE ``fitBounds`` to it. ``projectId`` gates the move to the active project (the per-user stream is not project-scoped). camelCase wire shape, matching the sibling events. Fire-and-forget / advisory: a dropped publish just means the camera doesn't move — the agent's text reply still names where it went.
    - `type` 'map_fly_to'
    - `projectId` string, uuid, required
    - `bbox` unknown[], required
      - unknown
  - `stream_chat_delta` StreamChatDelta — Schema for streaming chat content deltas.
    - `type` 'delta'
    - `display` 'prose'
    - `content` string, required — Incremental content from the agent.
  - `stream_thinking_delta` StreamThinkingDelta — Schema for streaming thinking/reasoning content deltas.
    - `type` 'thinking_delta'
    - `display` 'thinking'
    - `content` string, required — Incremental thinking/reasoning content from the agent.
  - `stream_tool_call_start` StreamToolCallStart — Schema for when a tool call begins.
    - `type` 'tool_call_start'
    - `display` 'tool_call_start'
    - `tool_name` string, required — Name of the tool being called.
    - `tool_call_id` string, required — Unique identifier for this tool call.
    - `args` object, required — Arguments passed to the tool.
  - `stream_tool_call_complete` StreamToolCallComplete — Schema for when a tool call completes.
    - `type` 'tool_call_complete'
    - `display` 'tool_call_result'
    - `tool_name` string, required — Name of the tool that was called.
    - `tool_call_id` string, required — Unique identifier for this tool call.
    - `result` unknown, required
    - `outcome` 'success' | 'failed' | 'denied' | 'interrupted' — Mirrors pydantic-ai's ToolReturnPart.outcome so the client can suppress success affordances for denied or interrupted calls.
  - `stream_chat_complete` StreamChatComplete — Schema for streaming chat completion event.
    - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
    - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
    - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
    - `type` 'complete'
    - `display` 'completion'
    - `new_messages` union[], required — Final complete messages from the agent run.
      - union
        - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
          - `parts` union[], required
            - union
              - …
          - `timestamp` string, date-time, nullable
          - `instructions` string, nullable
          - `kind` 'request'
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'interrupted'
        - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
          - `parts` union[], required
            - union
              - …
          - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
            - `input_tokens` integer
            - `cache_write_tokens` integer
            - `cache_read_tokens` integer
            - `output_tokens` integer
            - `input_audio_tokens` integer
            - `cache_audio_read_tokens` integer
            - `output_audio_tokens` integer
            - `details` object
          - `model_name` string, nullable
          - `timestamp` string, date-time
          - `kind` 'response'
          - `provider_name` string, nullable
          - `provider_url` string, nullable
          - `provider_details` object, nullable
          - `provider_response_id` string, nullable
          - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
    - `summarization_task_id` string, nullable — Celery task ID if chat summarization was triggered due to token limit.
    - `total_tokens` integer, nullable — Total tokens that triggered summarization, if applicable.
    - `project_id` string, required — Project ID the chat is associated with.
    - `structured_output` object, nullable — Structured output from the agent, if output_type was specified.
    - `pending_enrichments` PendingEnrichmentRef[], nullable — Skeleton enrichments created during initial project setup that are still being configured by the background task. Carried on the complete event so the live client can render pending enrichment cards immediately.
      - `id` string, required — UUID of the skeleton enrichment row.
      - `name` string, required — Internal name of the enrichment.
      - `display_name` string, required — User-visible name for the enrichment.
  - `stream_chat_error` StreamChatError — Schema for general streaming chat error event.
    - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
    - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
    - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
    - `type` 'error'
    - `display` 'internal'
    - `error` string, required — Error message.
    - `code` 'db_unavailable' | 'agent_failure' | 'internal' | 'run_in_progress' | 'not_continuable' | 'attachment_unavailable' — Stable taxonomy for generic stream errors. Clients branch on `code` for retry / toast / surface decisions; the free-form `error` message remains the user-visible string. Add new codes here when a new error class needs distinct client handling.
    - `partial_result` boolean — True when the workflow persisted a finalized partial turn before emitting this terminal (cooperative monitor stop). A resumed reader can't render replayed tool events, so it reloads history instead of treating this frame as fully rendered. False for generic errors and reader-synthesized hard-cancel terminals, where nothing new was persisted.
    - `continuable` boolean — True only when the failed turn left a partial the agent can resume from (the same condition that sets chats.last_turn_continuable). Distinct from partial_result: a monitor stop (timeout/lock-lost) persists a partial but is NOT continuable. The FE gates the Continue affordance on this, never on partial_result.
    - `new_messages` union[] — The persisted partial turn, surfaced and instruction-filtered. Populated with partial_result=True (a cooperative monitor stop or a terminal model failure that had already completed work), so the live client renders the stopped turn without leaving leaked <thinking> prose; empty otherwise.
      - union
        - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
          - `parts` union[], required
            - union
              - …
          - `timestamp` string, date-time, nullable
          - `instructions` string, nullable
          - `kind` 'request'
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'interrupted'
        - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
          - `parts` union[], required
            - union
              - …
          - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
            - `input_tokens` integer
            - `cache_write_tokens` integer
            - `cache_read_tokens` integer
            - `output_tokens` integer
            - `input_audio_tokens` integer
            - `cache_audio_read_tokens` integer
            - `output_audio_tokens` integer
            - `details` object
          - `model_name` string, nullable
          - `timestamp` string, date-time
          - `kind` 'response'
          - `provider_name` string, nullable
          - `provider_url` string, nullable
          - `provider_details` object, nullable
          - `provider_response_id` string, nullable
          - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
  - `stream_chat_interrupted` StreamChatInterrupted — Schema for chat interruption event. ``new_messages`` carries the persisted partial (surfaced) so the live client renders a stopped turn exactly as a reload would — routing any leaked ``<thinking>`` reasoning to the thinking display instead of leaving it as raw chat prose. Empty when no partial was persisted (a hard-cancel synthesis), so no partial turn is fabricated.
    - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
    - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
    - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
    - `type` 'interrupted'
    - `display` 'internal'
    - `reason` string, required — Human-readable interruption copy for display surfaces.
    - `project_id` string, required — The project ID.
    - `stop_reason` 'user_requested' | 'timeout' | 'lock_lost', required — Why a chat run was stopped — the cooperative flag's reason taxonomy. Carried in the Redis stop flag and, on the monitor's escalation path, in the durable stop event. ``USER_REQUESTED`` (and any unknown reason) synthesizes the generic ``interrupted`` terminal; the two monitor-initiated reasons get their specific error copy.
    - `partial_result` boolean, required — Whether a finalized partial turn was persisted before this terminal. Required, no default: every emitter declares persistence state explicitly, and only the workflow's cooperative finalize — which persists before emitting — may set True.
    - `new_messages` union[] — The persisted partial turn, surfaced and instruction-filtered. Populated only by the cooperative finalize (which persists before emitting); empty for reader-synthesized hard-cancel terminals.
      - union
        - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
          - `parts` union[], required
            - union
              - …
          - `timestamp` string, date-time, nullable
          - `instructions` string, nullable
          - `kind` 'request'
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'interrupted'
        - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
          - `parts` union[], required
            - union
              - …
          - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
            - `input_tokens` integer
            - `cache_write_tokens` integer
            - `cache_read_tokens` integer
            - `output_tokens` integer
            - `input_audio_tokens` integer
            - `cache_audio_read_tokens` integer
            - `output_audio_tokens` integer
            - `details` object
          - `model_name` string, nullable
          - `timestamp` string, date-time
          - `kind` 'response'
          - `provider_name` string, nullable
          - `provider_url` string, nullable
          - `provider_details` object, nullable
          - `provider_response_id` string, nullable
          - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
  - `stream_web_search_status` StreamWebSearchStatus — Schema for web search status updates from built-in tools.
    - `type` 'web_search_status'
    - `display` 'internal'
    - `status` string, required — Status of the web search operation.
    - `tool_call_id` string, required — Unique identifier for this tool call.
    - `content` object, nullable — Additional status data if available.
  - `stream_chat_user_message` StreamChatUserMessage — The acting user's prompt, emitted as the FIRST durable stream event. The live sender already rendered its own message optimistically, so it ignores this echo. A client that *reconnects* to an in-flight run (page refresh mid-stream) has no optimistic bubble — it replays the durable stream from offset 0 and renders the question from this event, then the deltas that follow. This makes the stream the single source of truth for an in-progress turn (persisted history only carries completed turns).
    - `type` 'user_message'
    - `display` 'internal'
    - `content` string, required — The user's prompt text for this turn.
    - `attachment` ChatAttachmentPayload — The receipt stamped onto a user turn's ``ModelRequest.metadata``. Once the turn is persisted this is the only record that a document was ever attached: the bytes are dehydrated out of the message before the row is written, and the frontend renders its attachment pill from this alone.
      - `attachmentId` string, required
      - `filename` string, required
      - `mediaType` string, required
      - `byteSize` integer, required
    - `timestamp` string, date-time — Turn start, stamped from the request-receipt anchor when the workflow first writes this event (falling back to the write instant for inputs persisted by an older release); durable-stream replay preserves the original value, so a reconnecting client anchors its elapsed-time display to the true start instead of the reconnect instant.
    - `invoked_skill` InvokedSkillPayload — The catalog skill a user turn invoked, for the inline chip.
      - `skillId` string, required
      - `skillName` string, required
  - `stream_chat_event` union — Schema type for streaming chat events union
    - StreamChatDelta — Schema for streaming chat content deltas.
      - `type` 'delta'
      - `display` 'prose'
      - `content` string, required — Incremental content from the agent.
    - StreamThinkingDelta — Schema for streaming thinking/reasoning content deltas.
      - `type` 'thinking_delta'
      - `display` 'thinking'
      - `content` string, required — Incremental thinking/reasoning content from the agent.
    - StreamToolCallStart — Schema for when a tool call begins.
      - `type` 'tool_call_start'
      - `display` 'tool_call_start'
      - `tool_name` string, required — Name of the tool being called.
      - `tool_call_id` string, required — Unique identifier for this tool call.
      - `args` object, required — Arguments passed to the tool.
    - StreamToolCallComplete — Schema for when a tool call completes.
      - `type` 'tool_call_complete'
      - `display` 'tool_call_result'
      - `tool_name` string, required — Name of the tool that was called.
      - `tool_call_id` string, required — Unique identifier for this tool call.
      - `result` unknown, required
      - `outcome` 'success' | 'failed' | 'denied' | 'interrupted' — Mirrors pydantic-ai's ToolReturnPart.outcome so the client can suppress success affordances for denied or interrupted calls.
    - StreamChatComplete — Schema for streaming chat completion event.
      - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
      - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
      - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
      - `type` 'complete'
      - `display` 'completion'
      - `new_messages` union[], required — Final complete messages from the agent run.
        - union
          - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
            - `parts` union[], required
              - …
            - `timestamp` string, date-time, nullable
            - `instructions` string, nullable
            - `kind` 'request'
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'interrupted'
          - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
            - `parts` union[], required
              - …
            - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
              - …
            - `model_name` string, nullable
            - `timestamp` string, date-time
            - `kind` 'response'
            - `provider_name` string, nullable
            - `provider_url` string, nullable
            - `provider_details` object, nullable
            - `provider_response_id` string, nullable
            - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
      - `summarization_task_id` string, nullable — Celery task ID if chat summarization was triggered due to token limit.
      - `total_tokens` integer, nullable — Total tokens that triggered summarization, if applicable.
      - `project_id` string, required — Project ID the chat is associated with.
      - `structured_output` object, nullable — Structured output from the agent, if output_type was specified.
      - `pending_enrichments` PendingEnrichmentRef[], nullable — Skeleton enrichments created during initial project setup that are still being configured by the background task. Carried on the complete event so the live client can render pending enrichment cards immediately.
        - `id` string, required — UUID of the skeleton enrichment row.
        - `name` string, required — Internal name of the enrichment.
        - `display_name` string, required — User-visible name for the enrichment.
    - StreamChatApprovalRequired — Terminal event for a run that ended awaiting tool approval (MAIA-2220). The agent issued a ``requires_approval`` tool call (``save_skill`` / ``update_skill``) and the run ended as a pending ``DeferredToolRequests``. The turn is persisted (including the pending call + its ``tool_approval_request`` metadata) before this frame is emitted, so the live client renders the approval card from ``new_messages`` exactly as a reload would. ``display: completion`` is the fail-safe: a consumer that doesn't know this discriminant still finalizes the turn's messages (per stream-terminal-events-carry-consumer-decision-state).
      - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
      - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
      - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
      - `type` 'approval_required'
      - `display` 'completion'
      - `new_messages` union[], required — Persisted turn messages, including the pending tool call.
        - union
          - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
            - `parts` union[], required
              - …
            - `timestamp` string, date-time, nullable
            - `instructions` string, nullable
            - `kind` 'request'
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'interrupted'
          - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
            - `parts` union[], required
              - …
            - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
              - …
            - `model_name` string, nullable
            - `timestamp` string, date-time
            - `kind` 'response'
            - `provider_name` string, nullable
            - `provider_url` string, nullable
            - `provider_details` object, nullable
            - `provider_response_id` string, nullable
            - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
      - `project_id` string, required — Project ID the chat is associated with.
      - `approval` ToolApprovalRequest, required — The pending approval-gated tool call, as persisted for card rendering.
        - `toolCallId` string, required
        - `toolName` string, required
        - `args` object, required
        - `details` object, nullable
    - StreamLLMProviderError — Schema for LLM provider-specific errors. A terminal model failure that already committed work finalizes on this frame rather than downgrading to a generic error — keeping the honest "providers responding slowly" provider taxonomy. The stamp + ``partial_result`` fields are additive (``partial_result`` defaults False, stamp fields default None), so the generic provider-error construction (``categorize_chat_stream_exception_to_frame``) is unchanged.
      - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
      - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
      - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
      - `type` 'llm_provider_error'
      - `display` 'internal'
      - `error_type` 'overload' | 'rate_limit' | 'api_error', required — Specific type of LLM provider error
      - `error` string, required — User-friendly error message.
      - `provider` string, required — LLM provider that caused the error (e.g., 'anthropic', 'openai')
      - `status_code` integer, nullable — HTTP status code if available
      - `retry_suggested` boolean — Whether the user should retry
      - `partial_result` boolean — True when the workflow persisted a finalized partial turn before emitting this terminal (a terminal model failure that had already completed tool work). A refresh-resumed reader reloads persisted history instead of treating this frame as fully rendered.
      - `continuable` boolean — True only when the failed turn left a partial the agent can resume from (the same condition that sets chats.last_turn_continuable). Distinct from partial_result: a turn can persist a partial yet not be continuable (e.g. a cooperative/monitor stop). The FE gates the Continue affordance on this, never on partial_result.
      - `new_messages` union[] — The persisted partial turn, surfaced and instruction-filtered. Populated with partial_result=True (a terminal model failure that had already completed work), so the live client renders the failed turn without leaving leaked <thinking> prose; empty otherwise.
        - union
          - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
            - `parts` union[], required
              - …
            - `timestamp` string, date-time, nullable
            - `instructions` string, nullable
            - `kind` 'request'
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'interrupted'
          - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
            - `parts` union[], required
              - …
            - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
              - …
            - `model_name` string, nullable
            - `timestamp` string, date-time
            - `kind` 'response'
            - `provider_name` string, nullable
            - `provider_url` string, nullable
            - `provider_details` object, nullable
            - `provider_response_id` string, nullable
            - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
    - StreamChatError — Schema for general streaming chat error event.
      - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
      - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
      - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
      - `type` 'error'
      - `display` 'internal'
      - `error` string, required — Error message.
      - `code` 'db_unavailable' | 'agent_failure' | 'internal' | 'run_in_progress' | 'not_continuable' | 'attachment_unavailable' — Stable taxonomy for generic stream errors. Clients branch on `code` for retry / toast / surface decisions; the free-form `error` message remains the user-visible string. Add new codes here when a new error class needs distinct client handling.
      - `partial_result` boolean — True when the workflow persisted a finalized partial turn before emitting this terminal (cooperative monitor stop). A resumed reader can't render replayed tool events, so it reloads history instead of treating this frame as fully rendered. False for generic errors and reader-synthesized hard-cancel terminals, where nothing new was persisted.
      - `continuable` boolean — True only when the failed turn left a partial the agent can resume from (the same condition that sets chats.last_turn_continuable). Distinct from partial_result: a monitor stop (timeout/lock-lost) persists a partial but is NOT continuable. The FE gates the Continue affordance on this, never on partial_result.
      - `new_messages` union[] — The persisted partial turn, surfaced and instruction-filtered. Populated with partial_result=True (a cooperative monitor stop or a terminal model failure that had already completed work), so the live client renders the stopped turn without leaving leaked <thinking> prose; empty otherwise.
        - union
          - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
            - `parts` union[], required
              - …
            - `timestamp` string, date-time, nullable
            - `instructions` string, nullable
            - `kind` 'request'
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'interrupted'
          - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
            - `parts` union[], required
              - …
            - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
              - …
            - `model_name` string, nullable
            - `timestamp` string, date-time
            - `kind` 'response'
            - `provider_name` string, nullable
            - `provider_url` string, nullable
            - `provider_details` object, nullable
            - `provider_response_id` string, nullable
            - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
    - StreamChatInterrupted — Schema for chat interruption event. ``new_messages`` carries the persisted partial (surfaced) so the live client renders a stopped turn exactly as a reload would — routing any leaked ``<thinking>`` reasoning to the thinking display instead of leaving it as raw chat prose. Empty when no partial was persisted (a hard-cancel synthesis), so no partial turn is fabricated.
      - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
      - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
      - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
      - `type` 'interrupted'
      - `display` 'internal'
      - `reason` string, required — Human-readable interruption copy for display surfaces.
      - `project_id` string, required — The project ID.
      - `stop_reason` 'user_requested' | 'timeout' | 'lock_lost', required — Why a chat run was stopped — the cooperative flag's reason taxonomy. Carried in the Redis stop flag and, on the monitor's escalation path, in the durable stop event. ``USER_REQUESTED`` (and any unknown reason) synthesizes the generic ``interrupted`` terminal; the two monitor-initiated reasons get their specific error copy.
      - `partial_result` boolean, required — Whether a finalized partial turn was persisted before this terminal. Required, no default: every emitter declares persistence state explicitly, and only the workflow's cooperative finalize — which persists before emitting — may set True.
      - `new_messages` union[] — The persisted partial turn, surfaced and instruction-filtered. Populated only by the cooperative finalize (which persists before emitting); empty for reader-synthesized hard-cancel terminals.
        - union
          - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
            - `parts` union[], required
              - …
            - `timestamp` string, date-time, nullable
            - `instructions` string, nullable
            - `kind` 'request'
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'interrupted'
          - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
            - `parts` union[], required
              - …
            - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
              - …
            - `model_name` string, nullable
            - `timestamp` string, date-time
            - `kind` 'response'
            - `provider_name` string, nullable
            - `provider_url` string, nullable
            - `provider_details` object, nullable
            - `provider_response_id` string, nullable
            - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
            - `run_id` string, nullable
            - `conversation_id` string, nullable
            - `metadata` object, nullable
            - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
    - StreamChatRetry — A dead model stream is being transparently retried (MAIA-2254). Emitted mid-stream when a transient transport failure (or stall) killed the in-flight model response and a fresh attempt is starting. The client must discard the in-flight assistant segment accumulated since the last completed tool boundary — the retry re-generates it — and may show a transient "retrying" state. Non-terminal: the turn continues with the fresh attempt's deltas, or ends with the usual error frame if the retry also dies.
      - `type` 'retry'
      - `display` 'internal'
      - `attempt` integer, required — 1-based model-call attempt now streaming (2 = first retry).
    - StreamWebSearchStatus — Schema for web search status updates from built-in tools.
      - `type` 'web_search_status'
      - `display` 'internal'
      - `status` string, required — Status of the web search operation.
      - `tool_call_id` string, required — Unique identifier for this tool call.
      - `content` object, nullable — Additional status data if available.
    - StreamSummarizationStatus — A follow-up send is waiting out the prior turn's summary finalization. Emitted once, before the send path starts polling for the summary child's slot release, so the client can show an accurate working state instead of silence. Non-terminal: the turn either proceeds into the normal stream or ends with a typed ``StreamChatError`` if the window outlives the wait.
      - `type` 'summarization_status'
      - `display` 'internal'
      - `message` string, required — User-facing description of the wait.
    - StreamProjectCreated — Schema for project creation event, sent at stream start.
      - `type` 'project_created'
      - `display` 'internal'
      - `project_id` string, required — The newly created project ID.
      - `project_name` string, nullable — Auto-generated project title, if available.
    - StreamChatUserMessage — The acting user's prompt, emitted as the FIRST durable stream event. The live sender already rendered its own message optimistically, so it ignores this echo. A client that *reconnects* to an in-flight run (page refresh mid-stream) has no optimistic bubble — it replays the durable stream from offset 0 and renders the question from this event, then the deltas that follow. This makes the stream the single source of truth for an in-progress turn (persisted history only carries completed turns).
      - `type` 'user_message'
      - `display` 'internal'
      - `content` string, required — The user's prompt text for this turn.
      - `attachment` ChatAttachmentPayload — The receipt stamped onto a user turn's ``ModelRequest.metadata``. Once the turn is persisted this is the only record that a document was ever attached: the bytes are dehydrated out of the message before the row is written, and the frontend renders its attachment pill from this alone.
        - `attachmentId` string, required
        - `filename` string, required
        - `mediaType` string, required
        - `byteSize` integer, required
      - `timestamp` string, date-time — Turn start, stamped from the request-receipt anchor when the workflow first writes this event (falling back to the write instant for inputs persisted by an older release); durable-stream replay preserves the original value, so a reconnecting client anchors its elapsed-time display to the true start instead of the reconnect instant.
      - `invoked_skill` InvokedSkillPayload — The catalog skill a user turn invoked, for the inline chip.
        - `skillId` string, required
        - `skillName` string, required
    - StreamChatTurnStarted — The promptless counterpart to ``StreamChatUserMessage``'s anchor. A continue-turn / tool-approval run carries no user message, so no echo is written and a reconnecting client would otherwise have no turn-start instant — its elapsed-time display restarts at the reconnect. This frame is emitted as the FIRST durable stream event exactly when the echo is not (one anchor carrier per run) and carries only the timestamp. The live sender already anchored at submit and ignores it; clients that don't recognize the type ignore it.
      - `type` 'turn_started'
      - `display` 'internal'
      - `timestamp` string, date-time — Turn start, stamped from the request-receipt anchor when the workflow first writes this event (falling back to the write instant for inputs persisted by an older release); durable-stream replay preserves the original value.
    - StreamChatCaughtUp — A resumed reader has drained the persisted backlog; frames after this are live output. Synthesized per-connection by the resume path at the replay/live boundary — never written to the durable stream, so it cannot itself be replayed. Before this frame, replayed text may belong to an already-invalidated model attempt (a ``retry`` frame later in the backlog voids it), so the client must hold it; after this frame, deltas are ordinary live output and paint like the send path. Non-terminal, no payload; clients that don't recognize the type ignore it and keep holding until a terminal.
      - `type` 'caught_up'
      - `display` 'internal'
  - `contact_model` ContactModel — Person contact model parsed from PDL Person Search API. This model represents a person with their contact information, current job, work history, and education.
    - `id` string, nullable — Unique identifier for the contact. Required for PDL contacts, auto-generated for web search contacts.
    - `full_name` string, nullable
    - `first_name` string, nullable
    - `middle_name` string, nullable
    - `middle_initial` string, nullable
    - `last_name` string, nullable
    - `last_initial` string, nullable
    - `job_title` string, nullable
    - `job_title_role` string, nullable
    - `job_title_sub_role` string, nullable
    - `job_title_levels` string[]
    - `job_title_class` string, nullable
    - `job_company_name` string, nullable
    - `job_company_id` string, nullable
    - `job_company_website` string, nullable
    - `job_company_size` string, nullable
    - `job_company_industry` string, nullable
    - `job_company_location_name` string, nullable
    - `job_company_location_locality` string, nullable
    - `job_company_location_region` string, nullable
    - `job_company_location_country` string, nullable
    - `job_start_date` string, nullable
    - `job_last_changed` string, nullable
    - `job_last_verified` string, nullable
    - `linkedin_url` string, nullable
    - `linkedin_username` string, nullable
    - `linkedin_id` string, nullable
    - `has_email` boolean
    - `has_phone` boolean
    - `has_personal_email` boolean
    - `has_work_email` boolean
    - `has_mobile_phone` boolean
    - `work_email` string, nullable
    - `personal_email` string, nullable
    - `mobile_phone` string, nullable
    - `phone` string, nullable
    - `location_name` string, nullable
    - `location_locality` string, nullable
    - `location_region` string, nullable
    - `location_country` string, nullable
    - `location_continent` string, nullable
    - `twitter_url` string, nullable
    - `twitter_username` string, nullable
    - `facebook_url` string, nullable
    - `facebook_username` string, nullable
    - `github_url` string, nullable
    - `github_username` string, nullable
    - `industry` string, nullable
    - `sex` string, nullable
    - `age_range` AgeRange — Age range from PDL. Can represent exact age (min == max) or range.
      - `min` integer, nullable
      - `max` integer, nullable
      - `is_approximate` boolean
      - `reasoning` string, nullable — The reasoning for the age range.
      - `sources` string[] — The sources for the age range.
    - `skills` string[]
    - `interests` string[]
    - `tenure` TenureRange — Job tenure range representing when someone started at their current position/company. Used for employment duration (e.g., via PDL data). Can represent exact year (min == max) or range of possible years. Confidence is inferred: exact year = confirmed, range = approximate.
      - `min` integer, nullable
      - `max` integer, nullable
      - `reasoning` string, nullable — The reasoning for the tenure range.
      - `sources` string[] — The sources for the tenure range.
    - `ownership_duration` OwnershipDuration — Property ownership duration representing when ownership began. Used for property/parcel ownership (e.g., via deed records). Can represent exact year (min == max) or range of possible years. Confidence is inferred: exact year = confirmed, range = approximate.
      - `min` integer, nullable
      - `max` integer, nullable
      - `reasoning` string, nullable — The reasoning for the ownership duration.
      - `sources` string[] — The sources for the ownership duration.
    - `identity_evidence` ContactIdentityEvidence — How this contact's identity was established, as a typed claim. ``corroborated`` is reserved for identities at least two independent sources agree on. The validator enforces a floor — two distinct non-empty source *strings* — so a bare or duplicated claim cannot be represented; whether the named sources are genuinely independent remains the agent's assertion. ``single_source`` states exactly what an unconfirmed vendor return is. ``conflicting`` flags a name that disagrees with the record's owner or target person.
      - `identity` 'corroborated' | 'single_source' | 'conflicting', required — corroborated: at least two independent sources agree this is the person. single_source: one source reported it and nothing independent confirms it. conflicting: the name disagrees with the record's owner or target person.
      - `identity_basis` string, required — What establishes (or undermines) the identity, in one sentence.
      - `corroborating_sources` string[] — Named independent sources backing the identity. At least two distinct sources are required to declare 'corroborated'.
      - `reachability_source` 'property_records' | 'published' | 'both', nullable — Where the phone/email came from; 'both' means both source types were used. A vendor match score is not evidence a number is reachable.
    - `experience` PDLExperience[]
      - `company` PDLCompany — Company information from PDL.
        - `name` string, nullable
        - `size` string, nullable
        - `id` string, nullable
        - `founded` integer, nullable
        - `industry` string, nullable
        - `location` PDLLocation — Location information from PDL.
          - `name` string, nullable
          - `locality` string, nullable
          - `region` string, nullable
          - `country` string, nullable
          - `continent` string, nullable
          - `metro` string, nullable
          - `geo` string, nullable
          - `street_address` string, nullable
          - `address_line_2` string, nullable
          - `postal_code` string, nullable
        - `linkedin_url` string, nullable
        - `linkedin_id` string, nullable
        - `facebook_url` string, nullable
        - `twitter_url` string, nullable
        - `website` string, nullable
      - `title` PDLTitle — Job title information from PDL.
        - `name` string, nullable
        - `role` string, nullable
        - `sub_role` string, nullable
        - `levels` string[]
        - `class` string, nullable
      - `location_names` string[]
      - `start_date` string, nullable
      - `end_date` string, nullable
      - `is_primary` boolean
    - `education` PDLEducation[]
      - `school` PDLSchool — School information from PDL.
        - `name` string, nullable
        - `type` string, nullable
        - `id` string, nullable
        - `location` PDLLocation — Location information from PDL.
          - `name` string, nullable
          - `locality` string, nullable
          - `region` string, nullable
          - `country` string, nullable
          - `continent` string, nullable
          - `metro` string, nullable
          - `geo` string, nullable
          - `street_address` string, nullable
          - `address_line_2` string, nullable
          - `postal_code` string, nullable
        - `linkedin_url` string, nullable
        - `linkedin_id` string, nullable
        - `website` string, nullable
        - `domain` string, nullable
      - `degrees` string[]
      - `majors` string[]
      - `minors` string[]
      - `start_date` string, nullable
      - `end_date` string, nullable
      - `gpa` number, nullable
    - `profiles` PDLProfile[]
      - `network` string, required
      - `id` string, nullable
      - `url` string, nullable
      - `username` string, nullable
    - `dataset_version` string, nullable
  - `tenant_lease_concise` TenantLeaseConcise — Concise tenant / lease model optimized for REIT screens. Includes a small set of critical fields and computed helpers (lease duration, months remaining, rent delta). Use `to_dict()` to include derived values in output.
    - `tenant_legal_name` string, nullable — Tenant legal name or primary occupant name.
    - `lease_start_date` string, date, nullable — Lease commencement / tenant move-in date (if known).
    - `lease_end_date` string, date, nullable — Lease expiration date for the current in-place term.
    - `in_place_rent_psf_yr` string, nullable — $ / SF / Year current in-place base rent (net of abatements if possible).
    - `expense_structure` 'nnn' | 'modified_gross' | 'full_service_gross' | 'industrial_gross' | 'other'
    - `escalation_type` 'fixed_percent' | 'cpi' | 'hybrid' | 'none' | 'other'
    - `occupancy_status` 'leased' | 'vacant' | 'owner_occupied' | 'partially_leased' | 'unknown'
    - `num_tenants` integer, nullable — Number of distinct tenants in the building (1 = single-tenant).
    - `delinquency_status` string, nullable — Description of the tenant's delinquency status.
    - `as_of` string, date-time — UTC timestamp when this record was compiled/last updated.
  - `owner_residential_mailing_address` OwnerResidentialMailingAddressModel — A resolved residential mailing address for a parcel's decision-making owner.
    - `street` string, nullable — Street line of the residential mailing address.
    - `city` string, nullable — City.
    - `state` string, nullable — Two-letter state code.
    - `zip_code` string, nullable — ZIP / postal code.
    - `resolved_owner_name` string, nullable — The person this address belongs to. For an entity owner this is the decision-maker the entity was pierced to, not the entity itself.
    - `owner_type` 'individual' | 'entity' | 'unknown' — Whether the parcel owner is an individual or an entity.
    - `is_available` boolean, required — True only when a reliable RESIDENTIAL mailing address was determined. False when only a business / registered-agent address was found — the caller must not persist a business address as a result.
    - `confidence` string, nullable — Qualitative confidence (e.g. high / medium / low).
    - `mobile_phone` string, nullable — Resolved owner's mobile phone, if known.
    - `landline_phone` string, nullable — Resolved owner's landline phone, if known.
    - `email` string, nullable — Resolved owner's email, if known.
    - `company` string, nullable — Company inferred for the resolved owner (from work email domain), if any.
  - `mortgage_profile` MortgageProfileModel — The full mortgage & liens profile for one matched property.
    - `free_and_clear` boolean, nullable
    - `open_lien_count` integer, nullable
    - `total_open_lien_balance` integer, nullable
    - `equity_percent` number, nullable
    - `estimated_value` integer, nullable
    - `foreclosure_status` string, nullable
    - `has_recorder_evidence` boolean, nullable
    - `as_of` string, nullable
    - `open_liens` MortgageOpenLien[]
      - `loan_amount` integer, nullable
      - `lender_name` string, nullable
      - `lender_type` string, nullable
      - `recording_date` string, nullable
      - `due_date` string, nullable
      - `loan_term_months` integer, nullable
      - `loan_type` string, nullable
      - `financing_type` string, nullable
      - `ltv` number, nullable
      - `current_estimated_balance` integer, nullable
      - `current_estimated_interest_rate` number, nullable
      - `estimated_payment_amount` integer, nullable
      - `heloc_flag` boolean, nullable
      - `private_lender` boolean, nullable
    - `valuation` MortgageValuationSummary — AVM-derived value and equity — modeled estimates, not recorded facts.
      - `estimated_value` integer, nullable
      - `equity_percent` number, nullable
      - `ltv` number, nullable
      - `equity_current_estimated_balance` integer, nullable
      - `confidence_score` integer, nullable
      - `as_of_date` string, nullable
    - `foreclosure` MortgageForeclosureNotice — Pre-foreclosure filing detail; only present when a filing exists.
      - `status` string, nullable
      - `recording_date` string, nullable
      - `auction_date` string, nullable
      - `unpaid_balance` integer, nullable
      - `past_due_amount` integer, nullable
      - `current_lender_name` string, nullable
    - `involuntary_liens` MortgageInvoluntaryLien[]
      - `lien_type` string, nullable
      - `document_type` string, nullable
      - `recording_date` string, nullable
    - `recorder_evidence` MortgageRecorderEvidence — County-recorder evidence booleans. Input to a county-level coverage rate, never a per-property coverage verdict — a never-refinanced home and an uncovered county share this signature.
      - `has_last_sale` boolean, required
      - `has_mortgage_history` boolean, required
      - `has_any_lien` boolean, required
  - `invalid_query_result` UnprocessableQueryResult — Result returned when a query cannot be processed — invalid input, system limitation, or execution blocker.
    - `reason` 'out_of_scope' | 'too_vague' | 'geographic_restrictions' | 'execution_blocked', required
    - `message` string, required — User-friendly explanation of why the query cannot be processed. Format rules: - Use third-person, declarative statements (NOT first-person like "I can't" or "I don't understand") - Do NOT ask questions (no "Did you mean...?" or "Can you specify...?") - Be concise and actionable - explain what's needed for a valid query
    - `suggestions` string[] — Optional list of example valid queries the user could try
    - `requirements_met` RequirementsCheck — Tracks which key requirements from the query are satisfiable.
      - `location` boolean, required — Whether the query specifies a geographic location
      - `size_range` boolean, required — Whether any size/area criteria can be satisfied (building roof area, parcel lot size, etc.)
      - `use_type` boolean, required — Whether property type, use type, or zoning criteria can be satisfied (from buildings, parcels, or places)
      - `contextual_data` boolean, required — Whether contextual data needs (enrichments, POIs) can be satisfied
  - `invalid_query_reason` 'out_of_scope' | 'too_vague' | 'geographic_restrictions' | 'execution_blocked'
  - `query_parse_response` QueryParseResponse — Lightweight real-time parse of a user query, used for live preview chips.
    - `items` QueryParseItem[] — Layers and enrichments identified from the query
      - `kind` 'layer' | 'enrichment', required — Whether a parsed item is a layer (from core dataset) or an enrichment.
      - `type` string, required — For layers: Overture Maps table name (e.g., 'division_area', 'building', 'place', 'segment', 'parcel'). For enrichments: enrichment tool name (e.g., 'General', 'Contacts', 'OwnerOccupied', 'TenantInfo').
      - `name` string, required — Human-readable name (e.g., 'Adams County Boundary', 'Commercial Buildings', 'Solar Potential')
      - `source_text` string, nullable — The exact substring from the user's query that this item was identified from
    - `requirements_met` RequirementsCheck, required — Tracks which key requirements from the query are satisfiable.
      - `location` boolean, required — Whether the query specifies a geographic location
      - `size_range` boolean, required — Whether any size/area criteria can be satisfied (building roof area, parcel lot size, etc.)
      - `use_type` boolean, required — Whether property type, use type, or zoning criteria can be satisfied (from buildings, parcels, or places)
      - `contextual_data` boolean, required — Whether contextual data needs (enrichments, POIs) can be satisfied
    - `warnings` string[] — Warnings for the user about the parsed geography.
    - `multi_geography_error` string, nullable — Error when the query targets multiple separate geographies (only one is supported per project)
  - `create_layer_failure` CreateLayerFailure — Model for a layer creation failure.
    - `message` string, required — A detailed message explaining why layer creation failed or what information is needed to try again. Include information about the strategy taken and why it failed. Suggest alternative strategies that could be tried. Limit to 100 words.
  - `select_question_payload` SelectQuestionPayload
    - `type` 'select'
    - `id` string, required
    - `prompt` string, nullable
    - `options` SelectOption[], required
      - `id` string, required
      - `label` string, required
      - `description` string, nullable
      - `sourceTable` 'parcel' | 'building' | 'place' | 'address' | 'school' — The table a resolved feature lives in — what a caller pulls its data from, and the feature's kind. A text match in the ``address``, ``place`` or ``school`` table resolves (point-in-feature) to a ``parcel``/``building``; a ``parcel`` attribute match resolves to the parcel itself.
      - `featureId` string, nullable — REQUIRED with sourceTable: the resolved candidate's id.
  - `confirm_question_payload` ConfirmQuestionPayload
    - `type` 'confirm'
    - `id` string, required
    - `prompt` string, nullable
    - `yesLabel` string, nullable
    - `noLabel` string, nullable
    - `destructive` boolean
    - `sourceTable` 'parcel' | 'building' | 'place' | 'address' | 'school' — The table a resolved feature lives in — what a caller pulls its data from, and the feature's kind. A text match in the ``address``, ``place`` or ``school`` table resolves (point-in-feature) to a ``parcel``/``building``; a ``parcel`` attribute match resolves to the parcel itself.
    - `featureId` string, nullable — REQUIRED with sourceTable: the resolved candidate's id.
    - `featureLabel` string, nullable — REQUIRED with sourceTable: the candidate's readable label. Names the layer the 'yes' creates; the system overwrites it with the hydrated label, so a fabricated value can't mislabel the layer.
    - `skillId` string, nullable — REQUIRED when this confirm question proposes running a skill: set to the matched catalog entry's id so the proposal links to the skill. Leave unset for ordinary yes/no questions.
    - `viewOffer` ViewOfferPreview
      - `proposedName` string, required — The name the View will be saved under if the user accepts.
      - `summary` string, required — One line stating what will be saved — the filters/slice that define the current set (e.g. 'Region = West, Status = Active'). Shown to the user in the offer card so they know what they're saving.
  - `interactive_question_answer` InteractiveQuestionAnswer
    - `optionId` string, required
    - `label` string, required
    - `questionId` string, required
  - `skill_plan_payload` SkillPlanPayload
    - `skill_name` string, required — Name of the skill being executed, as shown to the user.
    - `phases` SkillPlanPhase[], required — The complete, ordered phase list for this skill run — resend the FULL list with updated statuses on every call, not a diff.
      - `label` string, required — Short user-facing phase name, e.g. 'Filter parcels by zoning'.
      - `status` 'pending' | 'in_progress' | 'completed' | 'skipped', required
  - `report_actions_payload` ReportActionsPayload
    - `intent_id` string, required — Short stable id you pick for this unit of work (e.g. 'score-parcels'); reuse it to re-report the same unit on failure.
    - `summary` string, required — Plain-language description of the work, shown to the user in place of the raw tool steps — describe the outcome, never the SQL or table names.
    - `ok` boolean — True for the optimistic announcement as you start; resend with the same intent_id and False if the work fails.
  - `stream_chat_approval_required` StreamChatApprovalRequired — Terminal event for a run that ended awaiting tool approval (MAIA-2220). The agent issued a ``requires_approval`` tool call (``save_skill`` / ``update_skill``) and the run ended as a pending ``DeferredToolRequests``. The turn is persisted (including the pending call + its ``tool_approval_request`` metadata) before this frame is emitted, so the live client renders the approval card from ``new_messages`` exactly as a reload would. ``display: completion`` is the fail-safe: a consumer that doesn't know this discriminant still finalizes the turn's messages (per stream-terminal-events-carry-consumer-decision-state).
    - `last_edit_description` string, nullable — Human-readable description of what was changed, derived from tools called.
    - `last_edited_at` string, date-time, nullable — Timestamp of when the project edit was recorded.
    - `last_edited_by_name` string, nullable — Display name of the user who made the edit.
    - `type` 'approval_required'
    - `display` 'completion'
    - `new_messages` union[], required — Persisted turn messages, including the pending tool call.
      - union
        - ModelRequest — A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model.
          - `parts` union[], required
            - union
              - …
          - `timestamp` string, date-time, nullable
          - `instructions` string, nullable
          - `kind` 'request'
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'interrupted'
        - ModelResponse — A response from a model, e.g. a message from the model to the Pydantic AI app.
          - `parts` union[], required
            - union
              - …
          - `usage` RequestUsage — LLM usage associated with a single request. This is an implementation of `genai_prices.types.AbstractUsage` so it can be used to calculate the price of the request using [genai-prices](https://github.com/pydantic/genai-prices).
            - `input_tokens` integer
            - `cache_write_tokens` integer
            - `cache_read_tokens` integer
            - `output_tokens` integer
            - `input_audio_tokens` integer
            - `cache_audio_read_tokens` integer
            - `output_audio_tokens` integer
            - `details` object
          - `model_name` string, nullable
          - `timestamp` string, date-time
          - `kind` 'response'
          - `provider_name` string, nullable
          - `provider_url` string, nullable
          - `provider_details` object, nullable
          - `provider_response_id` string, nullable
          - `finish_reason` 'stop' | 'length' | 'content_filter' | 'tool_call' | 'error', nullable
          - `run_id` string, nullable
          - `conversation_id` string, nullable
          - `metadata` object, nullable
          - `state` 'complete' | 'incomplete' | 'suspended' | 'interrupted'
    - `project_id` string, required — Project ID the chat is associated with.
    - `approval` ToolApprovalRequest, required — The pending approval-gated tool call, as persisted for card rendering.
      - `toolCallId` string, required
      - `toolName` string, required
      - `args` object, required
      - `details` object, nullable
  - `tool_approval_request` ToolApprovalRequest — The pending approval-gated tool call, as persisted for card rendering.
    - `toolCallId` string, required
    - `toolName` string, required
    - `args` object, required
    - `details` object, nullable
  - `tool_approval_answer` ToolApprovalAnswer — The user's approve/decline for a pending tool call. ``message`` is wire-forward-compat: no UI affordance sends it yet (the card is approve/decline only), but the denial seam already threads it into the synthesized ToolDenied as context for a future decline-reason input. Capped because it persists into chat history and rides every subsequent model call.
    - `toolCallId` string, required
    - `approved` boolean, required
    - `message` string, nullable
  - `attribution_payload` AttributionPayload — One trust-receipt entry. Carried under two metadata keys with the same shape: `attribution` (the reply ran a skill / applied a saved preference) and `knowledge_saved` (the reply saved a new knowledge entry). The key distinguishes the semantic; for `knowledge_saved` `kind` is always `"knowledge"`.
    - `kind` 'skill' | 'knowledge', required
    - `entryId` string, required
    - `displayName` string, nullable
    - `scope` 'user' | 'workspace' | 'project' — Tier a knowledge entry applies to. Resolution is additive and ordered ``user -> workspace -> project`` — precedence is ordering only, not override (no shadowing in v1). "System" knowledge is the agent's existing system prompt — a separate, pre-existing mechanism that does not live in this table — so there is no ``system`` scope here. Project scope (MAIA-2547) is ambient: paths never name a project; the run's project id scopes reads and writes.
  - `invoked_skill_payload` InvokedSkillPayload — The catalog skill a user turn invoked, for the inline chip.
    - `skillId` string, required
    - `skillName` string, required
  - `chat_attachment_payload` ChatAttachmentPayload — The receipt stamped onto a user turn's ``ModelRequest.metadata``. Once the turn is persisted this is the only record that a document was ever attached: the bytes are dehydrated out of the message before the row is written, and the frontend renders its attachment pill from this alone.
    - `attachmentId` string, required
    - `filename` string, required
    - `mediaType` string, required
    - `byteSize` integer, required

---

[API](https://skmtc.net/maia-analytics/apis/maia-api.md) · [All operations](https://skmtc.net/maia-analytics/apis/maia-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/maia-analytics/maia-api/revisions/ab171f3fcfbe/schema)
