Skip to main content

Blueprint versioning and access

These endpoints are mounted under /api/blueprints/... and cover Blueprint Library inventory, versioning, and role-based access management for blueprint workspaces. Blueprint versioning follows the same edit and promotion model used by Genesis Apps: a blueprint can stay in standard mode or opt into versioned mode with immutable saved versions, a production assignment, and a mutable working copy. When authentication is enabled, Genesis resolves blueprint ownership from blueprint_manifest.yaml using the top-level created_by field, with fallback to legacy blueprint.created_by metadata and, when needed, origin.created_by_user_id. The owner automatically satisfies Blueprint RBAC checks on these routes, and newly created or copied blueprints are owner-only by default until you add role grants. When a caller imports a blueprint ZIP as a new local install or separate copy, Genesis sets created_by to the importing user so they can access the installed copy immediately; archive creator metadata is preserved in origin when the ZIP does not already include that block.
When authentication is disabled, Blueprint RBAC checks do not block requests on these routes.

HTTP authentication refusal responses

When an HTTP request is refused by the authentication middleware, Genesis returns the same JSON error envelope used by the rest of the API rather than a bare response string:
The error_type identifies the refusal category: authentication for 401, forbidden for 403, and service_unavailable for 503. Other server-side refusals use the shared internal category when no more specific status mapping exists. HTTP 410 Gone uses error_type: "gone"; for example, a dataset snapshot that is no longer available after a restart cannot be retried as-is and the dataset must be refreshed. Genesis also returns the same value in the X-Request-Id response header. Clients can use request_id or that header to correlate the refusal with server logs, including when HTTP request logging is disabled. Features unavailable in a deployment can return HTTP 501 with error_type: "not_available_here"; the error field gives the reason and available alternative. Browser-dependent features also use this response in a Databricks App deployment when Chromium is not included. Databricks App SCIM endpoints use this response for admin routes; the /scim/v2 routes return the corresponding SCIM error shape. For a missing or invalid credential, the error detail names the configured authentication provider and explains the accepted forms: an Authorization: Bearer <access token> issued by that provider or a Genesis service credential in the X-Genesis-Service-Token header. The detail also distinguishes authentication failure from a separate 403 permission refusal. API clients should preserve and display the full 403 refusal detail on affected routes: it includes actionable administrator guidance, not just a permission ID. See the RBAC Practical Guide for the permission labels. For the credentials most commonly used by API clients, the refusal identifies which credential was rejected: a Genesis personal access token uses Authorization: Bearer gp_... and points to POST /api/auth/pats to mint a new one; a sandbox thread token uses Authorization: Bearer gsk_... and expires with its sandbox run. These credentials are not interchangeable. Internal endpoints that explicitly require X-Genesis-Internal-Token report whether that header was absent or present but unrecognised and do not accept an Authorization: Bearer token or a session cookie as a substitute. Some refusals preserve a machine-readable error code and add existing response fields alongside the envelope. For example, a demo access denial continues to use error: "demo_access_denied" and may include reason, detail, and redirect_to. When the demo gate denies an identity, an HTML browser request is redirected with HTTP 303 to the refusal page; non-HTML requests receive HTTP 403 rather than a missing-credential 401. The hosted /mcp/ transport also returns 403 for this denial.

Genesis Desktop sign-in errors

GET /api/desktop/auth/login returns HTTP 400 with error_type: "bad_request" when the Genesis backend listens on a port that Genesis Desktop does not register for sign-in callbacks. The response’s payload.reason is sign_in_port_not_registered, and payload.sign_in_ports lists the supported ports (47080–47083). An invalid desktop.callback_relay_url remains a 400 validation error.

Genesis Desktop sign-in dialog

GET /api/desktop/auth/login returns data.authorize_url and data.state. Open the authorization URL in the system browser, then use the state to follow the sign-in while the Desktop dialog waits: The state query parameter and cancel-request field must contain 1–128 characters. A completed attempt reports succeeded or failed through the status endpoint. Before returning Desktop state or checking in an activated device, the service compares the saved sign-in target with the configured provisioner URL and identity-provider issuer. If either changed, it clears the prior sign-in and device activation and asks the person to sign in again; the install and its chats and data remain. Older installs without a saved target are treated as a different target when they have activation state. The current Desktop defaults are listed in Genesis Desktop sign-in targets. If Desktop cannot remove the Genesis AI key from the previous sign-in, the sign-in callback returns HTTP 503 with Genesis could not remove the AI key from your previous sign-in. Try again in a moment. It does not record the new sign-in target until removal succeeds, so the person can retry sign-in later. After a successful sign-in, the callback page confirms the signed-in identity and attempts to open Genesis Desktop through genesis-desktop://signed-in 300 ms after loading. The page also offers Return to Genesis Desktop as a link to open the app. The browser may ask permission to open it; if Genesis does not open, switch back to the Genesis window. When the provisioner refuses activation because the account’s email domain is not allowed, the activation result uses status: "not_allowed" and its message says Genesis Desktop isn't available for this account yet. Use a different account, or contact [email protected]. This is distinct from email_unverified, which remains the status for an account that needs email verification.

Genesis Desktop returning activation

The data.activation.llm.returning, data.activation.llm.plan_mode, and data.activation.llm.remaining_usd fields are returned by GET /api/desktop/status and the responses to POST /api/desktop/activate, POST /api/desktop/check-in, and POST /api/desktop/sign-out when an LLM activation is present. returning is true when the provisioner indicates an activation before this one; otherwise it is false. When a returning activation has no saved AI choice and no other AI provider is active, Genesis Desktop restores Genesis AI as the person’s choice instead of asking them to choose again. If another provider is active, Desktop does not replace it during this step and may read the person’s remaining Genesis AI credit from the provisioner.

Genesis Desktop Genesis AI-only mode

DesktopStatusData exposes these fields; both default to false: After showing the notice, acknowledge it with POST /api/desktop/genesis-ai-only-notice/seen. It takes no request body and returns BaseResponse; the notice is not due again after acknowledgment. This laptop-mode route returns 409 when Genesis Desktop mode is unavailable. Genesis Desktop also enforces this restriction on the server: attempts to configure or test another provider, change its chat or embedding assignments, or choose AI supplied by a saved connection are refused with HTTP 403. POST /api/desktop/llm-choice refuses the own choice with 403, and POST /api/desktop/credit/switch refuses a switch away from Genesis AI with 403.

Genesis Desktop saved credential notice

GET /api/desktop/status returns data.saved_credentials_unreadable, an integer that defaults to 0. When it is greater than 0, Genesis Desktop shows a notice asking the person to reconnect saved connections whose credentials this install’s encryption key cannot open. After the notice is dismissed, Desktop acknowledges it with POST /api/desktop/credentials-notice/seen; the request has no body and returns BaseResponse. The notice is not due again after acknowledgment. This laptop-mode route returns 409 when Genesis Desktop mode is unavailable. See Master encryption key for how Desktop stores and recovers its key.

Genesis Desktop Genesis AI allowance

GET /api/desktop/credit returns the current Genesis AI reading when Genesis AI is active. With fresh=false (the default), a cached reading can be about three minutes old. With fresh=true, or while a monthly refusal is recorded, readings older than about 15 seconds are refreshed. Desktop reads the provisioner’s allowance with the sign-in token while it is valid, then uses this install’s device credential after the token expires. This keeps the reading refreshable without another sign-in when the provisioner accepts device-credential entitlement reads. If the provisioner refuses that credential, the reading is unavailable and Desktop waits 180 seconds before retrying; a new sign-in resumes sign-in-token reads immediately. A successful reading also updates the stored activation’s plan when it differs from the provisioner’s reported plan. The Desktop allowance display uses percent_used and resets_at on the monthly plan without showing dollar amounts. The one_time plan keeps the existing dollar-based credit display. The response also includes the existing available, model, max_budget_usd, spend_usd, used_fraction, exhausted, heads_up_due, and alternatives fields. If Genesis AI is not active or a reading is unavailable, available is false; plan usage and reset fields are null when there is no reading.

Genesis Desktop trial status

The data.trial field in GET /api/desktop/status and the responses to POST /api/desktop/activate, POST /api/desktop/check-in, and POST /api/desktop/sign-out is present when the provisioner marks a trial for display; otherwise it is null. It reports the provisioner’s latest trial answer: For how trial status affects Genesis Desktop locking and display, see Genesis Desktop lock and origin controls.

Genesis Desktop version policy

The provisioner can include an app_version_policy object in activation, check-in, or entitlement answers to set a minimum or recommended Desktop version. The running app reports the resulting notice in GET /api/desktop/status; data.update_required and data.update_available are null when there is no applicable update: In these notices, min_version, recommended_version, and current_version use the display name for named Genesis Desktop builds: for example, 1.19.98-3 is returned as 1.19.98d3. Other version strings are returned unchanged. The provisioner policy fields are min_version, recommended_version, message, and download_url. The version fields are optional release-version strings such as 0.2.28; message is optional text, and download_url is an optional HTTPS URL. A required notice locks Desktop until it updates; a recommended-only notice does not lock it. A 426 refusal uses status: "update_required" and may include app_version_policy beside detail. It locks the app even if the response omits a minimum version. The app retains the latest policy in memory and checks again at the normal check-in cadence, so it can unlock when the provisioner lowers its minimum. For lock enforcement and in-progress agent turns, see Genesis Desktop lock and origin controls.

Genesis Desktop release-channel updates

The Desktop updater checks its install’s release channel with GET /api/desktop/update/{target}/{arch}/{current_version}. target and arch must each match [a-z0-9_]{1,32}; current_version must match [0-9A-Za-z.+_-]{1,64}. The provisioner selects the channel (dev, internal, or public) from the install’s device credential, and the endpoint returns that channel’s Tauri latest.json manifest as published. The route returns 204 when no newer version is available, 404 when Desktop laptop mode is unavailable, 409 when the install is not activated, and 503 when the provisioner update check fails. On 409 or 503, Desktop checks the stable feed instead. The release-channel check is also available from the Update now action on the Update required lock screen.

Genesis Desktop diagnostics

The Genesis Desktop trial build sends redacted diagnostic logs to its provisioner. Secrets are removed before upload. This behavior is limited to Genesis Desktop; hosted and self-hosted deployments do not send these diagnostics. The provisioner must advertise diagnostics in its latest check-in response’s accepts list for automatic uploads to run. At each start, after this process’s first successful check-in, the Desktop check-in job uploads new redacted log content since the previous accepted upload. This includes the previous session’s remaining logs and the current start’s first lines. If Desktop starts offline, this upload runs on a check-in tick after the first successful check-in. After that startup attempt, the job attempts another automatic upload at most once an hour, measured from the previous attempt. Each attempt sends at most four chunks; any remaining log content is sent on a later hourly attempt. Failed chunks are retried on that schedule, not on every check-in tick. Both automatic uploads require an activated device credential and a provisioner that advertises diagnostics in its latest check-in response. Each chunk contains up to 1,500,000 log bytes by default. If the provisioner’s latest check-in response also advertises diagnostics_gzip, Desktop sends gzipped chunks of up to 4,000,000 log bytes instead; Send diagnostics uses gzip under the same condition. If a provisioner responds with HTTP 413, Desktop halves the automatic chunk size and retries, down to 64,000 bytes, then retains that size for the rest of the process. The job also includes redacted log excerpts around recent failures in a check-in when the provisioner accepts diagnostics. A failure excerpt is generated for an error or crash, or a failed tool or query event. Genesis Desktop can also show a preview before a person chooses Send diagnostics. These laptop-mode routes return HTTP 404 when laptop mode is unavailable: Successful responses use { "success": true, "data": ... }. The preview’s data contains: Send the preview identifier in the request body:
The send response’s data contains the provisioner’s receipt id, and optional received_at and expires_at timestamps. The send route returns HTTP 404 if the preview expired, was already sent, or laptop mode is unavailable; 409 if the device is not activated; and 503 if the provisioner cannot accept the upload.

Genesis Desktop browser tools

These routes manage the browser driver and Chromium downloaded by Genesis Desktop. They are available only when the server runs in laptop mode; otherwise they return HTTP 403. The install and remove operations apply to the computer running Genesis, not to a remote browser client. Successful responses use { "success": true, "data": ... }. The data object contains: The install and remove routes return HTTP 501 when this Genesis does not manage downloadable browser tools. The Settings controls and first-use browser behavior are described in Advanced Settings — Browser tools and Browser Tools.

Genesis Desktop MCP clients

These laptop-mode routes inspect and connect MCP clients on the computer running Genesis. The client list is available only in laptop mode; otherwise it returns HTTP 403. Each client includes connectable (boolean). false means Connect cannot work for that client; connect_action gives the reason.

Desktop CLI connections

These routes detect and import connections configured in local CLIs and Git on the computer running Genesis. They are available only in laptop mode; otherwise they return HTTP 403. Detection never returns credential values. If gh has no github.com account, detection also checks Git’s credential helper and, when that check finds no sign-in, may run an SSH authentication check against github.com. That check can make one network round trip. The decline request accepts one to 100 candidate IDs, for example:
It returns { "success": true }. A successful import clears that candidate’s declined state.

Hosted identity lifecycle

Hosted deployments use auth.provider: hosted to verify signed assertions against the configured issuer, audience, JWKS URL, tenant, and installation. Verification alone does not authorize application access: the assertion subject must resolve to an active explicit identity link for that tenant and installation. Hosted lifecycle responses contain only the selected binding_id and its state; assertion claims and audit details are not returned. For the role-ownership rule that keeps hosted roles tied to active identity links during startup and revocation, see Hosted identity links.

POST /api/hosted-identities/initial-admin

Creates the sole initial administrator binding. This route requires a verified hosted management assertion with the hosted.identity.bootstrap scope.

POST /api/hosted-identities/invitations

Creates one ordinary hosted-user binding. The authenticated hosted user must have the manage_users permission.

POST /api/hosted-identities/{binding_id}/revoke

Revokes one exact binding selected by the path parameter. A hosted user needs manage_users; a management assertion needs the hosted.identity.emergency_revoke scope. The request body for the initial-admin and invitation routes is: The revocation body contains only operation_id with the same bounded string rules. Successful responses have the form:

Hosted WebSocket admission

In hosted mode, the estate-live, log, unified, sandbox-terminal, and CLI-terminal WebSockets require the same verified hosted assertion and active identity link before accepting the socket. Sandbox and CLI terminal access are then checked against the admitted user’s existing permissions and ownership rules. Hosted sockets are rechecked while connected; an expired or revoked identity closes the socket instead of leaving the session usable.

Blueprint library inventory endpoint

GET /api/blueprints/ returns the Blueprint Library inventory used by the web client. The inventory and agent capability catalog/search omit built-in blueprints marked hidden: true; those blueprints remain startable by name. See Hidden built-in blueprints for the affected blueprints and supported start routes. Malformed manifest fields do not remove the rest of the library from the index; see Library index resilience for the handling of the skipped entry. When authentication is enabled, Genesis filters out blueprints the caller cannot view. The same ownership resolution applies here, so owners continue to see their own blueprints without separate role grants. Callers also pass when their role includes view_all_blueprints or manage_all_blueprints.

GET /api/blueprints/

Blueprint library response fields

Key per-blueprint inventory fields

Blueprint authoring short-description field

The Blueprint editor also sends short_description on create and update requests. This field writes to blueprint.short_description in blueprint_manifest.yaml. Create requests also accept default_mode, which writes blueprint.default_mode:

Blueprint recommendation wizard endpoint

POST /api/blueprints/wizard powers the guided mission-creation flow. Genesis matches the caller’s free-text goal against the Blueprint Library and only considers blueprints the caller can execute. Depending on how specific the request is, the route returns either up to two clarifying questions or up to three ranked blueprint recommendations. When the caller has visible saved connections, Genesis may also use a compact RBAC-filtered connection summary as a soft signal about likely platforms. Missing or mismatched connections do not make a blueprint ineligible. After a client submits answers, Genesis does not start another follow-up round and instead returns type: recommendations. Follow-up calls can also include answer_context so Genesis receives the original question text alongside each answer instead of only question IDs.

POST /api/blueprints/wizard

Blueprint wizard answer-context item fields

Blueprint wizard response fields

When the advisor LLM omits type but returns exactly one non-empty, shape-valid array — questions (each item has id and text) or recommendations (each item has blueprint_name, numeric confidence, and rationale) — Genesis infers type server-side before returning the response. An absent or empty array, malformed items, an explicit but invalid type, or both arrays present without type still fail validation. Some Claude endpoints return the complete recommendation object as a JSON string in the top-level recommendations field. Genesis accepts this form only when the outer object contains exactly that field and its decoded object contains exactly type: "recommendations" and a non-empty recommendations array of valid items. Recommendation confidence must be numeric (not a boolean) and between 0.0 and 1.0, inclusive; out-of-range values fail validation.

Blueprint wizard question fields

Blueprint wizard recommendation fields

Blueprint wizard error responses

POST /api/blueprints/wizard returns a classified LLM error when the advisor cannot produce a usable result; clients should not treat it as an empty recommendation list. The response uses error_type: "llm_error", a safe top-level error message, request_id, and payload.llm_failure. The llm_failure object contains kind, message, action, and error_id; it does not include the provider’s raw error text. The response also includes the request ID in the X-Request-Id header. For the same structured failure metadata on chat messages and stream events, see Chat endpoints — working with threads.

Blueprint path parameter

Optional blueprint identity query parameter

Blueprint export endpoint

GET /api/blueprints/{blueprint_name}/export streams a ZIP of a custom (non-default) blueprint working copy. When the blueprint is versioned, Genesis also bundles saved version snapshots and embeds a blueprint_export_manifest.yaml sidecar.

GET /api/blueprints/{blueprint_name}/export

Current Genesis exports use manifest_version: 2 in the sidecar. In addition to blueprint_name, the manifest records blueprint_id, source_identity (source_type, source_id, repo_id, path), and working_copy_fingerprint so destination imports can match identity across environments. When the blueprint is versioned, the manifest also includes a versioning block with production and working-copy metadata plus per-version snapshot roots. Legacy exports without blueprint_id remain importable; the import preview marks them as legacy_export: true and requires explicit confirmation before Add as a new version can target an exact name match. If a saved version’s files are no longer available, Genesis completes the working-copy export but omits that version from the archive. The manifest lists omissions in versioning.versions_not_included and sets versioning.includes_all_saved_versions to false; if the omitted version was production, its reason warns that an imported archive will have no production pin for it.

Blueprint ZIP import endpoints

Blueprint ZIP uploads are limited to 64 MiB. Archives with more than 2048 members, oversized members, or unsafe compression ratios are rejected during validation. POST /api/blueprints/import/preview accepts multipart form field file and optional confirm_legacy_name_match (boolean, default false). The route validates a ZIP without changing files, registrations, or version history. The response identifies the incoming blueprint_name, optional blueprint_id, manifest_version, legacy_export, matching local target, allowed collision strategies, and recommended_strategy. When valid is false, message carries the validation error. Preview responses can also include: Older exports can be previewed without confirmation; the response marks create_version as requiring a legacy name-match confirmation when the export lacks blueprint_id. POST /api/blueprints/import accepts multipart form field file plus:
  • collision_strategy: create_copy (default), create_version, or replace.
  • promote_imported_version: optionally promote a newly created destination version when collision_strategy=create_version. Ignored for copy and replace operations. Requires Blueprint manage access on the destination.
  • confirm_legacy_name_match: required before a pre-v2 export can update an existing blueprint by exact name with collision_strategy=create_version.
  • override: backward-compatible boolean alias for collision_strategy=replace.
create_copy keeps the existing object and suffixes the imported name on collision. create_version requires one matching editable local blueprint_id, preserves destination-owned history and production, and appends one local version; identical content returns no_change=true. When the backing Git repository already exists, Genesis stages and commits the imported working copy with message Import blueprint version: {blueprint_name} so the import is visible through GET /api/blueprints/{blueprint_name}/history. replace changes only the working copy and preserves existing destination history. Default, WIP, ambiguous, and managed-repository targets reject version import with HTTP 409 and structured detail fields such as error_code, blueprint_name, and blueprint_id (for example BLUEPRINT_IDENTITY_MISMATCH, BLUEPRINT_VERSION_TARGET_NOT_UNIQUE, LEGACY_BLUEPRINT_CONFIRMATION_REQUIRED). Successful import responses include import_action, blueprint_name, blueprint_id, created_version_number, created_version_id, promoted, no_change, overrode_existing, and a human-readable message. For new installs and create_copy imports, Genesis assigns the importing user as manifest created_by so RBAC grants immediate view, edit, and manage access to the installed copy. When the archive lacks an explicit origin block, Genesis records the archive creator there (including legacy manifests that only stored created_by) before replacing the local owner. create_version and replace preserve the destination blueprint’s existing created_by and origin.

Blueprint detail endpoint

GET /api/blueprints/{blueprint_name} returns the resolved Blueprint manifest, phase payloads, source-selection metadata, and caller access flags used by the Blueprint detail page.

GET /api/blueprints/{blueprint_name}

Key Blueprint detail response fields

Blueprint file endpoint

GET /api/blueprints/{blueprint_name}/files/{file_path} serves a resource file from a Blueprint package. Use mission_id for a file read rendered in a mission when duplicate Blueprint names need to resolve to the Blueprint that mission recorded or ran.

Blueprint origin fields

The same origin schema is used by the inventory origin field and the detail payload blueprint.origin.

blueprint.source_view fields

Blueprint rename endpoint

POST /api/blueprints/{blueprint_name}/rename renames an existing local or remote-backed blueprint. The route rejects default blueprints, active WIP sessions, and blueprints that still have in-flight missions. Local renames take effect immediately. Remote-backed renames open a pull request and become effective after that pull request is merged and the next source sync applies the rename. Genesis also migrates completed mission references, saved versioning state, and Blueprint access grants to the new name. Requires Blueprint manage access and the system-level manage_git permission.

POST /api/blueprints/{blueprint_name}/rename

Blueprint rename response fields

Blueprint rename status behavior

Blueprint RBAC actions

The access-management endpoints accept these Blueprint action names:

Blueprint-wide role permissions

Genesis checks Blueprint access in two layers. First, it evaluates the per-blueprint policy: admin bypass, ownership, or an explicit blueprint:<name> grant for the requested action. If that does not allow access, Genesis falls back to role-level blueprint-wide permissions: Grant these on a custom role in Config → Users (or via RBAC role updates) to give members every existing and future blueprint without individual /api/blueprints/{blueprint_name}/access/grant calls. The built-in admin role includes both permissions by default.

Blueprint delete endpoint

DELETE /api/blueprints/{blueprint_name} permanently deletes a custom Blueprint without missions. If it has associated missions, Genesis hides it instead. When durable blueprint history is enabled (durable SQLite or git durability for the Apps repository), hard deletion also removes its saved versions, release mode, promotion history, and production assignment, and the caller must have manage access. The optional blueprint_id query parameter pins the request to a stable Blueprint identity; see Optional blueprint identity query parameter.

Versioning endpoints

A blueprint must already be in versioned mode before you can create, promote, or restore saved versions. When an operation needs saved-version files from an Apps repository still being restored after a restart, it can return 503 and be retried. A missing production assignment or unavailable production snapshot returns 404; conflicting or unsafe Apps-repository state returns 409.

Versioning summary fields

GET /api/blueprints/{blueprint_name}/versioning/summary returns these top-level fields:

Versioning request bodies

POST /api/blueprints/{blueprint_name}/versioning/enable

This endpoint does not document any request body fields.

POST /api/blueprints/{blueprint_name}/versioning/versions

POST /api/blueprints/{blueprint_name}/versioning/promote

POST /api/blueprints/{blueprint_name}/versioning/restore

Access-control endpoints

GET /api/blueprints/{blueprint_name}/access

Response fields:

POST /api/blueprints/{blueprint_name}/access/grant

Grant requests reject unknown role_name values with 400 Bad Request, for example: Unknown role 'analyst'. Choose one of the configured roles. Empty or whitespace-only role_name values return 400 with role_name is required.

POST /api/blueprints/{blueprint_name}/access/revoke

Action Review endpoints

These routes provide one generic lifecycle for governed actions: propose, inspect, decide, execute, and reconcile. Proposal creation and reconciliation require APPROVE_AGENT_ACTION. Inbox listing accepts either APPROVE_AGENT_ACTION or view_connections: a signed-in caller without APPROVE_AGENT_ACTION sees Prover requests only, while a caller with it continues to see every authorized action family. Reading or acting on one proposal also requires APPROVE_AGENT_ACTION, except registered Prover requests, for which view_connections is sufficient. Approve and reject remain person-only. For Prover requests, any signed-in person with view_connections may decide only when they can also read the subject the request decides on: its comparison, finding, or view test cases. A request whose subject is hidden is answered as an unknown proposal (404), including for a caller who holds APPROVE_AGENT_ACTION. The person’s own PAT is accepted and attributed to its owner, while an agent’s own call, the system identity, and an app-only token are refused. Execute and reconcile also enforce the authenticated caller and deployment tenant boundary. The registry admits crm.merge.v1, the first Action Review action, and the Prover evidence-request action prover.request_finding_evidence.v1. The CRM action’s shipped default posture is require_review, so proposals for it enter the review lifecycle before execution; there is no configuration key that enables its action type. Salesforce and HubSpot CRM merge routes and connector tools now submit crm.merge.v1 proposals through this lifecycle while retaining their compatibility facades and legacy response shapes. The Prover action type is used by the request_evidence tool; its approval flow and result are documented there. Reviewer reads are scoped to the authenticated human and the deployment tenant. Rows for action scopes the caller cannot access are omitted, and detail, status, approval, rejection, execution, and reconciliation requests do not grant access just because the caller knows a proposal ID. Prover requests are also filtered by whether the caller can see the comparison or finding the request decides on, using the same visibility rule as the corresponding Prover read. Hidden requests are omitted from the inbox and total_count; requests beyond the visibility probe budget are withheld and reported as incomplete with incomplete_reason=probe_budget.

Action Review route summary

POST /api/action-review/proposals request fields

GET /api/action-review/proposals query fields

For inbox listing, the service checks APPROVE_AGENT_ACTION once per request to choose whether to apply the Prover-only family filter; the RBAC lookup is served from memory once RBAC is warm. A caller without that permission sees Prover requests only, with the family filter applied in the ledger query before the bounded scan. Other action families therefore neither consume that caller’s scan ceiling nor make the Prover-only result incomplete. In either case, a Prover request is listed and counted only when the caller can read the subject it decides on. Prover subject visibility is checked independently of visible_threads_only. When the scan reaches a Prover request, it makes one indexed read per distinct finding or view boundary, up to 100 subjects. For a view boundary with snapshots, visibility requires access to every snapshot. For a boundary with no snapshots, as with a pair prepared by prepare_migration_prover, visibility requires access to every connection sealed in the request (source, target, and any warehouse connection). A request that sealed no connections uses the pair recorded for its boundary instead; an unreadable or missing pair is visible only to a caller with manage_all_connections. The same visibility rule applies when retrieving or deciding a Prover request. Requests beyond the subject budget are withheld; the response sets truncated=true and incomplete_reason=probe_budget. Submitting a Prover request also requires the caller to see its comparison, finding, or view boundary; an unseeable subject is refused as unknown before the request is stored. Reconciling an uncertain Prover request applies the same subject check before the handler reads it. The list is bounded: reviewer-scope checks are performed before offset paging, and the response can be incomplete when Genesis cannot examine every candidate or must omit a candidate. Use truncated and incomplete_reason to distinguish an incomplete view from an empty inbox. The wire values for incomplete_reason are probe_failure, probe_budget, handler_unavailable, row_unreadable, and scan_ceiling. Only one value is returned, using this precedence order: probe_failure > probe_budget > handler_unavailable > row_unreadable > scan_ceiling. The page limit is not an incomplete reason; compare total_count with the number of returned proposals to detect it. The detail route accepts include_events as a boolean query parameter, default true, and view as full or summary, default full. Set include_events=false when the caller needs the reviewer dossier without the durable event trail. view=full parses and returns the stored review payloads; view=summary returns the status view, linked record, expected_effect, requester_kind, bot_id, decided_by, decided_by_label, and decision_reason. It provides the request’s state, approval lifetime, and decision context without producing the omitted review payloads. When include_events=false, events is absent; when it is true, events is an array that can be empty. For a Prover disposition-package approval request, set include_package_signability=true to include viewer_can_sign, viewer_can_apply, and viewer_cannot_sign_reason for this caller. The server performs one package-state fold to produce these fields, so the option is opt-in; the package read already carries the same verdict when that page is available. For a Prover package request withdrawn because an agent’s bundle adopted it, the detail response also includes bundle_supersession with bundle_id, outcome, superseded_by_proposal_id, and superseded_by_url. The outcome is superseded when the bundle request was created, or bundle_request_not_created when it was not; in the latter case the package can be requested again. These fields are null until the supersession event is sealed. If include_events=false, this field costs one ledger event read for an adopted package request; other requests do not incur that read. When a bundle request holds the adopted package, detail also returns can_renew: false, ownership_reason (bundle_request_live, bundle_declined, or bundle_policy_denied), and holding_bundle_request with the owning request’s identity and decision details. Checking this ownership reads the package owner; in view=summary, when the response has no intent, it also reads the proposal to locate the package coordinates.

POST /api/action-review/proposals/{proposal_id}/approve and /reject

Both decision routes accept the same body: Approval and rejection are separate operations by default. For a proposal that the current human is allowed to execute, POST /api/action-review/proposals/{proposal_id}/approve-and-execute combines the approval and the single execution attempt. Its body uses the same optional reason field. The response reports success, decided, claimed, final_status, execution_refusal_reason, and the optional execution_refusal_detail and execution_error_detail. success is true only when final_status is executed; a refusal is returned with HTTP 200 and success: false. When a handler or provider proves that it refused before any write was applied, final_status is not_applied, and the handler’s reason and detail are returned in execution_refusal_reason and execution_refusal_detail. When the effect may have applied, final_status is reconciliation_required; do not retry the action, and use execution_error_detail for the provider’s error when the handler captured it. Approval refusal issues no grant, while refusal before claiming leaves the grant unspent. A decision can be refused with 409 Conflict when the proposal or current policy no longer matches the decision context. Some actions have no separate apply step: approving them is the whole action. Every proposal read (status, list rows and detail) carries approving_is_the_action, and it is true for those actions, currently Prover test-case approval (prover.approve_view_test_cases.v1) and learning adoption (learning.adopt.v1) and local command runs (local_cli.run.v1; see run_local_command). For such a proposal, POST .../approve also applies it in the same request, exactly as approve-and-execute does, and its response adds the same claimed, final_status, execution_refusal_reason, execution_refusal_detail and execution_error_detail fields; success is then true only when the change was applied. The proposal stays authorized only when the apply could not start (the policy could not be reloaded, or the executor was refused before the claim); claimed is then false and execution_refusal_reason says why. execution_refusal_reason: "claim_lost" means another executor holds or has already spent the grant. claimed: true with no final_status (outcome_not_recorded) means the attempt ran but its outcome was not stored; do not retry it. For every other proposal, approve answers success, decided, refusal_reason and proposal only.

Decision hand-back to the agent

After a human approval or rejection is committed, Genesis sends the decision and optional reason to the registered agent conversation that raised the proposal. This applies whether the person decides in the Action Review page or through these API routes. A tool call made outside a Genesis conversation (over MCP or /api/tool_call) does not receive this hand-back; its caller must poll action_review_request_status for the request it opened. A delivery failure does not undo the decision; the default-on Action Review cleanup sweep retries a hand-back that was not delivered durably. The sweep cadence is controlled by action_review.sweep_interval_seconds (default 600 seconds; see Action Review settings). The Prover disposition-package action type is prover.approve_disposition_package.v1. Its handler compares the package’s approval projection digest before writing and applies every staged item in one strict transaction or none. The Action Review proposal’s expected_effect identifies each finding’s disposition and durability; the package’s overall durability is the furthest-reaching item level. A successful execution returns an execution receipt that links the Action Review proposal and digest to the Prover decision event; a drift, unavailable promotion, or lock conflict is reported as not applied rather than as a partial package approval. The composite Prover bundle action type is prover.approve_composite_bundle.v1. It is created when a review bundle carries one or more disposition packages, and its sealed projection includes the bundle’s rules, packages, and evidence-gap decisions. Execution applies every carried member in one transaction or none. The native bundle /approve endpoint does not accept this grant; approve the linked Action Review proposal instead. The improvement-docket action type is prover.decide_improvement_docket_entry.v1, one request per finding. Its sealed projection names each open element, the finding’s disposition, and the licence approving installs. Execution accepts every element in one transaction or none. Declining leaves the elements open. The native docket /accept endpoint refuses an agent-filed entry; approve its Action Review request instead. Generic HTTP execution does not carry a trusted tool or Blueprint runtime origin. When an authorized proposal is bound to a non-empty origin, the execute route refuses it with claimed=false and refusal_reason=execution_origin_required before claiming the single-use grant, so the human approval remains available for execution from its originating runtime.

Action Review response fields

Status responses contain success and a proposal object with these fields: The list response returns success, proposals, total_count, offset, limit, truncated, and incomplete_reason. Each row includes effective_status, requester identity, tenant, optional bot and thread identifiers, decision metadata, and the review payload selected by dossier_scope: headline adds a dossier holding only its title and first record, and full adds the whole dossier plus expected_effect. When include_statement=true, a row also includes requested_statement for an action that runs a statement; otherwise that field is null. The list response also includes visible_threads_only, which is true when the caller requested thread-visibility narrowing. The detail response additionally returns parsed stored payloads, including execution_receipt once an execution has produced one, and carries the effective status in status rather than in a separate effective_status field. Execute responses report success, claimed, final_status, an optional refusal_reason, an optional refusal_detail, an optional error_detail, and execution_result when available. success is true only when final_status is executed; not_applied means the handler or provider proved that no write was applied, while reconciliation_required means the effect may have applied and must not be retried. When available, error_detail is the provider’s own error text. Reconcile responses report resolved, final_status, and an optional refusal_reason. If Action Review storage fails with a database error, the endpoint returns 503 Service Unavailable with a generic message directing operators to the server log; raw driver error text is not included in the response. This is separate from error_detail, which describes an action handler or provider execution result when available. For governed Linear writes (ticket.write.v1), the default-on cleanup sweep can settle a reconciliation_required item without retrying the write when the item recorded a shared linear connection that has personal_credentials set to disabled, does not use User OAuth, and does not require double-key access. The sweep reads through that recorded connection’s service credential rather than a default connection, then verifies the Linear organization and authenticated user against the original attempt. If any check fails, the item remains unresolved. When a stored by-reference literal cannot be rejoined, reviewer reads and authority-bearing operations return 409 Conflict with code: "proposal_text_unavailable"; the proposal cannot be shown, approved, or executed, but it can still be rejected or revoked. If a proposal is too large to store, Genesis refuses it before writing and returns 413 Payload Too Large. Action Review also runs a default-on cleanup sweep in the background. Each pass materializes expired proposals and reconciles claims that have remained claimed past their stale threshold; expiry is still enforced when decisions and claims are evaluated, so the sweep makes the resulting state visible rather than providing the enforcement itself. Configure its cadence and stale-claim age in Advanced Settings.

Blueprint history endpoint

This route returns commit history from the backing Git repository. When authentication is enabled, it requires the system-level view_git permission rather than a Blueprint action grant.

GET /api/blueprints/{blueprint_name}/history

In a Databricks App, these routes follow the repository’s Git safety policy. See the Git policy.

Blueprint import endpoint

The canonical preview, request fields, collision strategies, compatibility behavior, and response contract are documented in Blueprint ZIP import endpoints. The legacy override=true form field remains supported as an alias for collision_strategy=replace; new clients should send the explicit strategy.

Blueprint repository sync and dev-session remediation

POST /api/blueprints/repositories/{source_id}/sync and POST /api/blueprints/{blueprint_name}/dev-session can return a structured remediation payload when the requested refresh or workspace setup fails for an actionable reason, such as an expired saved Git OAuth grant. For these routes, Genesis returns HTTP 409 when error_code is OAUTH_RENEWAL_REQUIRED. The structured payload is returned in the FastAPI error detail body so clients can render a recovery CTA instead of treating the response as a session-auth failure.

Blueprint sync remediation payload fields

Blueprint sync remediation action fields

Blueprint mission-start sync warning fields

POST /api/blueprints/{blueprint_name}/start keeps remote release refresh best-effort. If Genesis cannot refresh the remote release before execution, the mission can still be created from the previously synced release and the success payload can include these warning fields:

Blueprint mission-start request fields

POST /api/blueprints/{blueprint_name}/start accepts the following model fields in the JSON body (alongside mission_name, description, kickoff_notes, agent_id, mode, source_context, repo_id, and optional blueprint_id): When llm_provider or llm_tier is omitted, Genesis resolves the value from the selected agent_id before persisting mission kickoff defaults on the thread. Phase context_defaults.model_tier overrides the mission kickoff tier while that phase is active. See Model tier during blueprint missions. When inputs is present, Genesis validates each key against the blueprint manifest inputs.required and inputs.optional names, stores the normalized object on the mission thread as blueprint_inputs, and includes it in the initial kickoff message. When journey_context is present, Genesis stores blueprint_journey_context on the thread and inherits it across blueprint_chain handoffs. For the system_test blueprint’s release and sandbox-trial inputs, including their model-selection behavior, see System Test mission guidance. For local_file_uploads, use the session_id supplied by the Launchpad upload flow and pass the returned file_ids unchanged. The session id is an opaque presenter- value; uploads are authenticated to the caller and are consumed after the Mission’s immutable client_files/manifest.json snapshot is created. Invalid, incomplete, duplicate, or unauthorized file references reject the start without leaving a partially created Mission.

Blueprint journey definitions

Resolve one declarative journey variant (for example the database-migration recipe’s snowflake or databricks platform variant).

GET /api/blueprints/journey-definitions/{journey_id}

Response data (BlueprintJourneyDefinition):

Blueprint mission-start execute access

With authentication enabled, POST /api/blueprints/{blueprint_name}/start and POST /api/blueprints/{blueprint_name}/runs require the caller to have execute access to the resolved Blueprint. A caller who can view a Blueprint may still receive HTTP 403 when no applicable owner, administrator, or role permission allows execution. This denial is distinct from the agent-access 403; its response includes these top-level fields: The execute action and role-wide permissions are described in Blueprint RBAC actions.

Blueprint mission-start tool policy preflight

POST /api/blueprints/{blueprint_name}/start and POST /api/blueprints/{blueprint_name}/runs run the same high-risk tool policy preflight before Genesis creates a mission project or thread. When the selected blueprint requires tools that are globally blocked under High-Risk Tool Controls, Genesis returns HTTP 409 with error_type policy_blocked. The ErrorResponse.payload object includes: Preflight inspects phase context_defaults tool groups (for example code_executor_tools or dbt_action) and phase text that references dbt_action, execute_python_code, or _run_sqlite_cli_commands. Model tier selection (llm_tier / llm_provider on mission start and context_defaults.model_tier per phase) is unrelated to this preflight.

Programmatic BlueprintRun endpoints

These routes let a host application start and supervise a blueprint mission without holding the mission chat UI. Full integration guidance, gate contracts, and the Atlas reference app are in Programmatic BlueprintRun. The caller needs system view_git permission, access to the run’s agent and mission project, and the repository must be associated with that project. Unlike direct Git repository routes, this run-scoped route does not perform the repository view check described under Git permissions. In a Databricks App, the repository-bundle route applies the same Git safety policy as other agent-writable repositories; see Git Action Tools. POST commit routes accept optional Idempotency-Key (max 120 characters). Replay with the same key and body returns 204; a different body returns 409 (idempotency_key_mismatch). Genesis carries the authenticated request’s user context into the kickoff and turns created by gate submissions, so Prover permission checks in those turns use the API caller. The optional actor field is audit metadata and does not supply that identity; see Programmatic BlueprintRun. Project detail payloads can include blueprint_run_id when the mission thread is linked to a programmatic run.

Required-check waiver

Call the waiver route only after reviewing why the server-owned check cannot be run or pass. The request body contains a human-authored reason string from 1 to 2,000 characters:
The response identifies the exact pinned check and includes blueprint_name, phase_id, script_sha256, waived: true, and idempotent. Repeating the same waiver for the same phase-entry snapshot is safe and returns idempotent: true. A waiver is recorded as platform provenance and clears the required-check gate only when all checks in the active phase are satisfied or waived.

BlueprintRunCreateRequest fields

BlueprintRunInputRequest fields

Mission and project list endpoints

Genesis missions are backed by projects. The Mission Center UI loads its mission list from GET /api/projects.

PUT /api/projects/{project_id}/status

Request body: The response includes the persisted result of the change:

Read-only project record endpoints

These endpoints provide the Goals page’s read-only view of typed project records. They are separate from Mission projects and organizational Projects. Records are created and updated through the project tools. The list endpoint accepts these optional query parameters: Both endpoints require authentication and return { "success": true, "data": ... }. The list keeps at most the 100 newest records. truncated is true when more records exist than the response contains. A different person’s record returns the same 404 as an unknown project_id. List errors include 401 and 500; detail errors include 401, 404, and 500. Without these filters, listing projects uses three database reads plus a user read per distinct person named in the records. A thread_id with no matching project uses one database read and no user reads; setting include_people=false skips the person-name reads for matching records as well. The list data contains items, people, truncated, and generated_at. Each item includes id, name, project_type, principal, opened_by, thread_id, created_at, closed_at, goal_total, status_counts, last_note, plan_incomplete, and planned. thread_id, created_at, closed_at, and last_note may be null; closed_at is set when the project is retired. plan_incomplete is true when the plan the project came from has gaps. planned maps each constrained goal to {"total": <expected assets>, "done": <assets with a passing check at their current version>}. people maps person IDs to display names or email addresses, or null when no name is available. A non-null last_note contains goal, at, actor, principal, kind, verification, status, and person_turn. The detail data includes id, name, project_type, parameters, principal, opened_by, thread_id, created_at, note_sequence, goals, notes, assets, and people. Goal ordering follows the project_read contract; notes are oldest first in server sequence. Each goal includes its id, name, status, acceptance_criterion, skill_ref, preconditions, asset_refs, and input_versions. For a met goal, input_versions records the versions its proof is based on: the last met note’s versions when the goal declares asset_refs, or the latest passing version of each asset cited by its checked evidence otherwise. These are assets registered to that goal, except that a goal met before assets belonged to goals may include an asset registered to another goal. It is empty unless the stored goal status is met. A note includes id, sequence, goal, actor, principal, at, evidence, asset, finding, waive_reason, person_turn, verification, verification_reason, server_check_result, previous_status, status, and input_versions. The detail read does not re-check evidence; verification describes the check when the note was recorded. Goals may also include status_qualifier (planned_scope when a met status applies only to the plan’s constrained scope) and planned with total expected assets and done passing at their current versions. Each goal includes server_records, newest first, for work the server filed under that project and goal whether or not it has been cited. A record includes kind (foundry_copy_receipt, notebook_publish, notebook_overwritten, or databricks_run), evidence_id (a Foundry copy receipt, published notebook version, overwrite mark, or Databricks run id), asset_id (the dataset or pipeline), target_table (the copy target or notebook path), state, reason, attempts, created_at, and updated_at; kind, target_table, reason, attempts, and timestamps may be null. Foundry copy states include copy_running, copied, copied_no_receipt, and copy_failed; notebook publishing and run submission use published and run_submitted. Recording outcomes include recorded_passed, recorded_failed, registered, replaced, not_readable, not_checkable, and project_retired; future states may be returned as well. attempts is how many times the server has checked the work; an open record with at least one attempt and a reason is waiting for another check. The detail read also selects every row in the project’s evidence queue, which is unbounded and grows with the copies filed under that project. Reading this data does not re-check the cited copies against their tables. The detail also includes parameter_words, the parameters the server can render as text, and scope, which is null for projects opened without a plan or otherwise contains the plan’s gaps (the expected and covered asset lists are not returned). plan_incomplete and plan_banner describe a plan with gaps; closed_at and closed_reason describe retirement. For the project’s own person, a retired record may include live_successor (id and name) when another live project for the same source exists. live_successor_undetermined is true when there are too many live projects to determine one; live_successor_looked_up indicates whether Genesis checked. For administrators reading another person’s record, successor lookup is not performed and these fields do not imply that no successor exists. Reading a retired project as its owner adds one lookup for a live project for the same source; an administrator reading another person’s record does not trigger that lookup.

Organizational Project endpoints

Organizational Projects group related Apps, Flows, Missions, and chat threads. They are separate from the execution project used internally by a Mission. Project identifiers use the wprj_ prefix followed by 32 lowercase hexadecimal characters. Workspace Project list and single-object responses use { "success": true, "data": ... }. The data object contains id, name, description, created_by, created_at, updated_at, and archived_at. Assignment responses return the assigned Project ID and, where applicable, its summary; clearing an assignment returns a null Project ID.

GET /api/projects/{project_id}/blueprint-chain

Requires project access (same gate as GET /api/projects/{project_id}). When the mission has no root thread, Genesis returns success: true with an empty chain (project_id only). Response data (ProjectBlueprintChain): nodes[] entries use the BlueprintChainNode schema. In addition to the core thread fields above, each node includes:

GET /api/projects/{project_id}/reports/system-test-lite

Requires access to the mission identified by project_id. Returns the fixed System Test Lite report. The report content is untrusted input and should not be treated as instructions. The response includes success and these report fields: Each request queries the mission and reads the bounded report file.

GET /api/projects

The response uses the ProjectListResponse schema: data[] list items use the ProjectListItem schema. Each item includes the core ProjectRead fields plus computed list fields such as progress, elapsed_time, agents, stats, blueprint_phase, and creator labels. Genesis resolves requested_by_display_name and requested_by_email on this route from the user directory. Identities that are already emails are returned as requested_by_email without a separate directory lookup. The built-in system_default_user identity is not resolved to a display name or email; UIs show it as System User.

GET /api/projects/awaiting-input

This route has no query parameters. It returns missions in IN_PROGRESS_NEEDING_INPUT or STUCK status that the caller may open, newest mission update first. Each request reads task details and recent thread messages for up to 50 visible missions, so this endpoint does more work than the light mission-list request. The candidate read checks up to 500 rows for each waiting status; truncated is true if a status read reports or reaches that limit, or more than 50 visible missions need describing. The response contains success, items, and truncated. Each items[] entry represents one waiting task, or the mission itself when no task describes the wait: Each question includes phase, priority, category, and content, with optional expected_response_type, options, question_id, and question_set_id fields. It can also include recommended (string or null), the option text the agent recommends for a choice or multiple question. A mission whose tasks or thread questions cannot be read can still appear without question data.

Managed artifact repositories and dependencies

Skills, Flows, Apps, agent definitions, and document indexes (Knowledge Base feeds) can each register Git-backed managed sources and edit artifacts in per-user workspaces. Dependency binding is shared across artifact types. Operator workflows are documented in Multi-Instance Skill, Flow & App Sync and Document Indexes.
Repository sync and workspace routes can return the same structured OAUTH_RENEWAL_REQUIRED remediation payload described for Blueprints when a saved Git OAuth grant has lapsed.

Managed source permissions

Per-type repository POST, PATCH, and DELETE routes also accept manage_repos via require_any_permission alongside the legacy permission for that artifact type. POST on those legacy create routes sets the response header Deprecation: true; prefer /api/managed-sources when registering a repository that should scan more than one artifact type. Deleting a source through a per-type route when scan_types_enabled contains more than one type returns HTTP 409 with error_code SOURCE_HAS_MULTIPLE_TYPES and admin_url /api/managed-sources/{source_id}. Use DELETE /api/managed-sources/{source_id} instead.

Unified managed sources (/api/managed-sources)

Requires manage_repos. Creating or updating a source that includes roles, or applying a role preview, also requires manage_users (HTTP 403 when absent). When scan_types_enabled includes agents, routes that touch agent discovery, sync, or per-artifact enablement also require manage_agents (HTTP 403 when absent). Use this surface when one Git repository should discover Skills, Flows, Apps, Blueprints, agent definitions, document indexes (Knowledge Base feeds), and (when enabled) Missions from a single repo_id checkout. The dashboard page is Config → Managed Sources (also requires access_config_panel). The Document Indexes page exposes the same panel through Sync from Git.

Flow pending-promotion endpoints

These endpoints manage Flow definitions held after a managed-source sync when auto_promote_on_sync is false. They require manage_flows. The promotion request also returns 401 when the acting user is missing, 403 without manage_flows, 404 when the pending change or source is gone, and 400 for other promotion validation failures.
When a managed-source sync or preview cannot resolve release_ref, its error identifies the requested ref and reports available release tags, branch refs tried, local branches, and whether a remote is configured (or that remote state could not be read).
Managed-source responses include release_poll on each source object from list, detail, create, and update operations. It is null when skill revision tracking is disabled or the source does not scan skills. Otherwise it reports the scheduled check state: checked_at, skipped_at, and error; new_release (tag, commit_sha); moved_tag (tag, anchored_commit_sha, nullable remote_commit_sha, detected_at); and upgrade_refusal (tag, error_code, message, at). A null remote_commit_sha means the pinned tag was deleted remotely. Findings describe conditions that still hold; a failed check sets error and retains the findings from the previous check. Each source response also includes auto_sync (default false) and auto_sync_state. The state reports whether the scheduled job is running, whether the source is currently eligible and any ineligible_reason, the last recorded outcome, checked_at, skipped_at, optional counts in waiting, and sanitized failures. Outcomes are up_to_date, updated, updated_awaiting_promotion, updated_with_errors, failed, or not_eligible. checked_at changes when the outcome changes, not on every unchanged pass; the first unchanged pass after an update records up_to_date. skipped_at records when the latest pass left a source alone because it was syncing. waiting counts items needing administrator action by kind: blueprint_versions, agent_changes, document_indexes, flow_changes, and role_changes. For role changes, it also reports counts by reason: roles_edited_locally, roles_owned_elsewhere, roles_rejected, roles_removed_from_source, roles_granting_unmanaged_agents, roles_changed_during_sync, roles_waiting_for_review (when auto-promote is off), and role_errors. auto_sync_state.role_notes lists rejected roles to fix in Git and removed roles to delete manually in Admin → Roles; these notes are not waiting work. Their reasons are visible only to callers with manage_users. For agent changes, waiting also reports counts by reason: agents_edited_locally, agents_switched_off_locally, agents_without_applied_record, agents_changed_during_sync, agent_conflicts, and agent_errors. failures contains by_type counts, degraded, fixed-name exceptions, and an optional error_code. If sync resolves release_ref to a tag that now names a different remote commit, it fails with error_code: RELEASE_TAG_CHANGED_ON_REMOTE rather than using the stale local tag. Reset the checkout and sync again, or re-pin the release tag for a governed skill source.
Managed-source deletion is retryable. A source remains marked as pending deletion until dependent cleanup succeeds, so a failed delete does not lose the ownership records needed for a retry or allow a deleted legacy source to reappear after reload. While cleanup is pending, sync returns HTTP 500 with error_code SOURCE_DELETE_CLEANUP_PENDING; resolve the reported cleanup_warnings and retry DELETE /api/managed-sources/{source_id}.
Async managed-source sync. POST .../sync-async returns immediately with a job_id. Poll GET .../sync-status/{job_id} until status is complete or error. The dashboard polls every 2 seconds for up to 15 minutes. Job status is held in memory (up to 200 terminal jobs); restarting the server drops in-flight and completed job records, so polling after restart returns HTTP 404. A second sync-async for the same source with a different overwrite_indexes, apply_agent_changes, expected_agent_commit, expected_agent_plan_hash, apply_role_changes, expected_role_commit, or expected_role_plan_hash starts a new job (serialized behind the first); matching values on all seven fields reuse the in-flight job.
Background governed skill release changes. On PATCH /api/managed-sources/{source_id}, send both sync_now: true and sync_in_background: true to save and audit a governed skill release or policy change, then run its sync as a background job. The response has sync_job (job_id, status, expected_release_commit) instead of sync; poll GET /api/managed-sources/{source_id}/sync-status/{job_id} until complete or error. The job is complete only when both the sync result and current source name the expected commit and the source is no longer pending. Otherwise it ends with error_code SKILL_RELEASE_CHANGED_DURING_SYNC or SKILL_RELEASE_STILL_PENDING. If no job starts after the change is saved, the response has warning code SKILL_RELEASE_SYNC_NOT_STARTED and no sync_job. Another release change is refused with HTTP 409 and error_code MANAGED_SOURCE_SYNC_IN_PROGRESS while a sync or release change is in progress. For other saves with sync_now: true, sync_in_background is ignored and sync completes before the response. Job records are process-local and are lost when the server restarts.
Managed agent sync and apply. Agent sources discover one subdirectory per agent under agents_root, each with an agent.yaml or agent.yml manifest (schema_version, agent_id, name, instructions, optional description, llm_provider, default tool/skill groups, and tool/skill access modes). Sync without apply_agent_changes only checks out Git content and returns agent_preview (summary, changes, commit_sha, plan_hash) plus requires_agent_apply_confirmation when any change is not noop. To apply, call sync again with apply_agent_changes: true, expected_agent_commit set to the preview’s commit_sha, and expected_agent_plan_hash set to the preview’s plan_hash. Missing preview pins return error_code AGENT_PREVIEW_REQUIRED; commit or plan drift after preview returns AGENT_PREVIEW_STALE. Managed agents are non-editable in the UI (is_editable: false); disable them per artifact or by removing the source (owned agents are deactivated, not hard-deleted).Scheduled sync agent apply. When scheduled sync runs for an agent source whose auto_promote_on_sync is not false, Genesis applies the discovered agent changes without a separate confirmation. The sync result includes agent_auto_apply with applied and waiting; changes it cannot safely apply remain for an administrator, and set requires_agent_apply_confirmation: true. See auto_sync_state.waiting above for the waiting counts and reasons. With auto_promote_on_sync: false, scheduled sync previews the agent changes but does not apply them unattended.Managed role sync and apply. A source with roles scans roles/<role_name>/role.yaml (or role.yml) role definitions. Manual sync returns role_preview with summary, changes, commit_sha, and plan_hash, and sets requires_role_apply_confirmation when applicable; an administrator with manage_users confirms the preview using its commit and plan hash. Preview details and reasons are visible only to callers with manage_users; secret grant names also require manage_secrets. When scheduled sync is active for the source (auto_sync: true) and auto_promote_on_sync is omitted or not false, Genesis applies safe role creates and updates automatically only when every agent granted by the role is managed by that same source. Other applicable changes wait for review; rejected definitions must be fixed in Git, and roles removed from Git remain on the instance until deleted in Admin → Roles. With auto_promote_on_sync: false, scheduled sync records a preview but does not apply roles unattended. The next scheduled pass checks the source (300 seconds by default; see Managed source automatic sync). Role definitions cannot grant admin-level access; see Users and Roles for the definition restrictions.
ManagedSourceUpsertResponse and sync outcomes. POST and PATCH return the full upsert envelope at the top level (success, source, sync, optional sync_job, warning / warning_code / warning_action). When sync_now runs, inspect sync for partial_success, degraded_artifact_count, hydrate_errors, and per-type entries under sync.hydrate (for example skills.degraded_artifacts with artifact_id and reason). A completed sync with lenient-recovered SKILL.md or flow YAML frontmatter still sets sync.partial_success to true and lists the artifact under the matching hydrate.*.degraded_artifacts entry. Config → Managed Sources surfaces these as warning toasts rather than plain success.
SCAN_TYPE_REMOVAL_NOT_SUPPORTED on PATCH. When scan_types_enabled would remove one or more existing types, the route returns success: false with error_code, error, current_scan_types, attempted_scan_types, removed_scan_types, recovery (delete_and_recreate), and remediation (delete the source and recreate with the desired scan_types_enabled).
document_feed_summary document counts. Each entry in indexes includes count (markdown files under Git management in that index) and index_doc_count (count plus index_only_doc_count — documents present in the live index but not yet in the repo). total_count sums managed count values only; total_index_doc_count sums index_doc_count across every index and is the accurate total for UI chips such as Config → Managed Sources “N documents” rows. Per-index fields also include index_only_doc_count, pending_change_count, proposer, pr, adopted, name_conflict_count, and requires_overwrite_ack.
checkout_exclude (managed checkout pruning). checkout_exclude is an ordered string[] on create, update, and ManagedSourceResponse that controls which repository paths Genesis omits from the managed base checkout using Git sparse checkout. It is separate from include / exclude, which only govern artifact discovery and enablement — those selectors do not change what is present on disk. Each rule must not contain NUL or embedded newline characters. Blank lines and lines starting with # are ignored. Other lines use gitignore-style path patterns; prefix a line with ! to re-include a path inside a broader exclusion. When checkout_exclude is absent or empty ([]), Genesis performs a full checkout (same as before this field existed). On PATCH, omitting checkout_exclude preserves the stored rules; sending checkout_exclude: [] disables sparse checkout on the next sync. Non-empty rules require Git 2.25 or newer — otherwise sync returns error_code SPARSE_CHECKOUT_UNSUPPORTED. Failed sparse apply attempts return SPARSE_CHECKOUT_APPLY_FAILED or MANAGED_CHECKOUT_DEGRADED when rollback also fails. Paths pruned from the base checkout are not present in the Git Browser tree or other filesystem enumeration of that checkout. Per-user managed worktrees (skills, flows, apps, agents, and document workspaces) stay on a full checkout even when the base checkout is sparse.

Runtime skill catalog routes

The execution response’s data contains execution_id, bot_id, skill_name, version, status, started_at, finished_at, duration_ms, invoked_by, input_payload, evidence, and error. A caller without manage_skills cannot execute a skill; the POST route returns 403. For shell-step payload substitution and its security boundary, see Skill Runtime Tools.
DELETE /api/skills/{skill_name} operates on the runtime skill registry (SkillService). To remove an imported AgentSkills package and its on-disk files, use DELETE /api/agent-skills/{skill_name} instead.

Skill repository and workspace routes

App repository and workspace routes

On GET /api/apps/repositories, artifact_counts includes only apps whose microapps record was created by managed sync (created_by starts with managed_sync:). Standalone apps whose workspace_path was mis-pathed into a managed clone after a failed export are excluded so Managed Sources counts stay accurate. Artifacts with no microapps record yet are still counted; if the microapps service is unavailable, Genesis falls back to counting all registry artifacts for that source.
Flow routes mirror the Skill pattern under /api/flows/... with flows_root on create and optional auto_promote_on_sync. Flow create and update validation also applies the bulk_copy contract before saving; see Flows — Move a table between registered connections for the accepted fields and target-specific rules. In a flow definition, steps[*].run_concurrently is a boolean that defaults to false; consecutive steps marked true run as one batch, with at most 8 steps at once.

Managed artifact dependency routes

Blueprint example requests

Team Wiki endpoints

These endpoints are mounted under /api/wiki/v1/... and provide Team Wiki browsing plus per-page commentary for the dashboard. Read responses cover both graph-backed entity pages and generated wiki://... subgraph documents. Graph-backed page responses are rendered dynamically from the Context Graph and can include graph-backed properties, lineage, ancestry, child-navigation metadata, related subgraph-document references, and a learning count for the requested node. Commentary routes let callers list, post, and soft-delete Markdown comments on any wiki page; only the comment author can delete a live comment, and soft-deleted comments return an empty body_markdown in list responses. The same page payload also backs the Digital Twin node detail panel and graph-backed entity pages in the standalone Wiki workspace when the frontend wiki feature flag is enabled. The page endpoint is optimized for the web UI: graph-native pages return the main rendered markdown without waiting for the ## Children rollup, and clients can load children separately from /api/wiki/v1/tree?parent=<qualified_name> when needed.
When authentication is enabled, all Team Wiki routes in this section require the system-level view_git permission, including commentary list, create, and delete.

Endpoint summary

GET /api/wiki/v1/tree

Team Wiki tree response fields

Team Wiki tree node fields

GET /api/wiki/v1/subgraphs

This route has no request parameters. It returns the generated wiki://... document refs used to populate the standalone Wiki discovery pane. When Twin cannot enumerate those documents, the route returns 503 instead of an empty success response. An empty subgraph_documents list therefore means Twin was reachable and no generated documents were found.

Team Wiki subgraph list response fields

Team Wiki subgraph list error responses

GET /api/wiki/v1/pages/{qualified_name:path}

Team Wiki page response fields

Team Wiki page error responses

Subgraph document reference fields

GET /api/wiki/v1/subgraph/{qualified_name_tail:path}

Team Wiki subgraph document response fields

Team Wiki subgraph document error responses

GET /api/wiki/v1/pages/{qualified_name:path}/commentary

Team Wiki commentary list response fields

Team Wiki commentary row fields

POST /api/wiki/v1/pages/{qualified_name:path}/commentary

Create one Markdown comment on the requested wiki page. Genesis attributes the comment to the authenticated caller and returns 201 Created on success.

Team Wiki commentary create response fields

Team Wiki commentary create error responses

DELETE /api/wiki/v1/pages/{qualified_name:path}/commentary/{comment_id}

Soft-delete one commentary row. Genesis verifies that the comment belongs to the path qualified name and that the authenticated caller is the author.

Team Wiki commentary delete response fields

Team Wiki commentary delete error responses

Configuration and secrets permissions

These endpoints back the Genesis Config UI and related administrative flows. They are mounted under /api/config/..., /api/secrets/..., /api/push-metrics/..., and selected /api/genesis-enhancements/... routes.
When authentication is disabled, RBAC permission checks do not block requests on these routes.
Routes in the groups below either enforce the listed system-level permission or, when marked Public, do not require an RBAC permission. Protected routes return 403 Permission denied when the caller lacks access.

Configuration permissions

Protected configuration and secrets endpoints

Configuration request bodies on these routes reject undeclared JSON fields with HTTP 422 Unprocessable Entity; fields are no longer silently ignored. Send only the fields listed for the endpoint, including for partial updates and connection tests. This applies to the request models used by the configuration, provider, automation, profile, secret, branding, SCIM, and custom-endpoint routes below. On PostgreSQL or SQLite metadata deployments, GET /api/config/warehouse/status reports a successful non-Snowflake status with is_snowflake: false, no current warehouse, and an empty available_warehouses list. The application name remains GENESIS_DATA_AGENTS. On those same deployments, the email settings endpoints report and manage the saved default email in the metadata database. GET /api/config/email/status returns that address when configured, with is_validated: false; email sending and test sends are unavailable because they require a Snowflake notification integration. DELETE /api/config/email clears the saved address.

LLM provider delete request fields

The DELETE routes for /api/config/llm-providers/openai, /api/config/llm-providers/cortex, /api/config/llm-providers/databricks, /api/config/llm-providers/litellm-proxy, and /api/config/llm-providers/other accept the same JSON request fields: These request models reject undeclared fields instead of ignoring them. Use sub_provider—not provider—when deleting a LiteLLM sub-provider; an unknown field returns request validation feedback rather than deleting the base provider or reporting that no base configuration exists.

Encryption key endpoints

These routes back the Encryption Key Configuration card on Config → Secrets and related setup flows in Snowflake Native App (SPCS) deployments. All require manage_secrets. The /encryption-key/enable and /encryption-key/verify-setup routes return 400 with "Not running in Native App mode" when SPCS_MODE is not TRUE. First-time setup SQL is shown by the dashboard and generates the key inside Snowflake; no route generates, returns or receives key material.

GET /api/secrets/encryption-key/status response

When status is unavailable, treat encryption as not confirmed missing — retry after the underlying Snowflake or external-secret outage clears. Older backends may return only configured and source; clients map that to status: "configured" or status: "missing".

POST /api/secrets/encryption-key/enable response

Returns 503 when the database connector is unavailable.

GET /api/secrets/encryption-key/verify-setup response

Embedded Tailscale auth-key endpoints (Native App)

These routes back the Tailscale tab of Config → Secrets in Snowflake Native App (SPCS) deployments. They return 400 with "Not running in Native App mode" when SPCS_MODE is not TRUE. All require manage_secrets. The Tailscale auth key never crosses these APIs — operators paste it into Snowflake SQL locally.

GET /api/secrets/tsnet-auth-key/status response

When enabled is false, online is false and backend_state is omitted.

GET /api/secrets/tsnet-auth-key/setup response

POST /api/secrets/tsnet-auth-key/enable response

Returns 503 when the database connector is unavailable.

GET /api/secrets/tsnet-auth-key/verify-setup response

GET / PUT /api/secrets/tsnet-auth-key/egress

GET returns the stored egress allow-list read from APP1.TSNET_EGRESS (instance schema). When unset, the response is ["0.0.0.0:443"] — the broad default that permits outbound TCP/443 to any host. PUT accepts a JSON body: Validation trims blanks, de-duplicates (order-preserving), requires at least one entry, allows at most 256 entries, and enforces host:port shape (DNS name or IPv4 host, port 1–65535). Returns 400 on invalid input. Both routes return: The saved list takes effect on the running EAI only after the operator re-connects Tailscale Control Plane External Access in Snowsight, then suspends/resumes the Genesis service.

GET / PUT /api/secrets/tsnet-auth-key/advertise-tags

GET returns the operator-set Tailscale ACL tag(s) read from APP1.TSNET_ADVERTISE_TAGS (instance schema). When unset or the table is missing, the response is an empty string. The value is folded into CORE.GET_TSNET_BOOT_CONFIG() at container boot (one round-trip with the auth key). PUT accepts a JSON body: Validation trims the input, de-duplicates tags (order-preserving), and rejects blank tokens inside a non-empty list (stray or doubled commas). Each tag must match tag:<name> where <name> is letters, digits, or hyphens (starting alphanumeric). Allows at most 32 tags and a total stored length of 512 characters. Returns 400 on invalid input. Both routes return: The saved value takes effect on the device only after the operator suspends and resumes the Genesis service (read at container boot via CORE.GET_TSNET_BOOT_CONFIG()). Required for the OAuth-client auth path when the auth key is an OAuth client secret — Tailscale requires --advertise-tags in that case.

POST /api/secrets/tsnet-auth-key/disable response

Disables embedded Tailscale for Native App deployment by calling CORE.DISABLE_TSNET_ACCESS() (reverts the auth-key UDF, removes the Tailscale EAI from the running service, unbinds references). The stored egress allow-list is left intact for a later re-enable.

GET /api/secrets/tsnet-auth-key/disable-template response

External Access Integration (EAI) assignment endpoints

These routes back Assign to Genesis in the Config UI for Snowflake Native App / SPCS deployments. All require access_config_panel. Genesis stores each binding as its own EAI_CONFIG row keyed by the reference identity (EAI_NAME = reference('<reference_name>')), not by EAI_TYPE. Multiple references can share the same type (for example genesis_external_access and tailscale_external_access both use CUSTOM); assigning one reference updates only that row and does not replace bindings for other references. On startup, Genesis also seeds one EAI_CONFIG row per external-access reference on the running service (splitting Snowflake’s comma-joined external_access_integrations list) so disable/cleanup paths such as CORE.DISABLE_TSNET_ACCESS() can match each reference independently.

GET /api/config/eai/{reference_name}/assigned query fields

POST /api/config/eai/assign request fields

Returns success: false with error: "Invalid EAI reference name or type" when reference_name or eai_type contains characters outside letters, digits, or underscore. When reference_name is genesis_external_access, a successful assign may also return refreshed_connection_egress with updated connection egress preflight states. Assignment does not treat a non-throwing CORE.UPGRADE_SERVICES() call as success. That procedure returns Services upgraded successfully even when it attached nothing (for example when the service had no prior external-access list and ALTER SERVICE was skipped). After the upgrade, Genesis reads SHOW SERVICES and confirms the reference is on GENESISAPP_SERVICE_SERVICE (the service CORE.UPGRADE_SERVICES upgrades). The same verification runs on POST /api/genesis-enhancements/eai/assign.

POST /api/config/eai/assign response fields

EAI assignment verification object

Returned in verification on POST /api/config/eai/assign, and POST /api/genesis-enhancements/eai/assign. Callers should treat assignment as failed only when checked is true and applied is false. When checked is false, Genesis does not fail a request that otherwise completed — for example local SQLite has no live service to inspect. The verification object uses exact reference identity matching on GENESISAPP_SERVICE_SERVICE, not the looser substring matching GET /api/config/eai/{reference_name}/assigned may use for status display.

Config setup apply endpoints

These routes validate and apply YAML or JSON configuration through the setup API. All require access_config_panel.

POST /api/config/setup/apply vs POST /api/config/setup/apply-partial

POST /api/config/setup/apply fully replaces persisted configuration: it clears existing APP_CONFIG values, writes only the supplied paths, drops fields not in the schema, and relies on schema defaults at read time for omitted fields. POST /api/config/setup/apply-file uses the same full-replace semantics for uploaded YAML or JSON. Use these routes for profile import and full-environment activation flows. POST /api/config/setup/apply-partial merges the payload on top of the current database state. Only paths present in the body are written; every other field keeps its existing value. Use this for targeted YAML/JSON patches where a full replace would be too destructive. A field’s storage semantics depend on the field’s schema, not on the write door: apply-partial and the Advanced Settings UI both write through the same ConfigApplier, so any given field is persisted identically whichever path sets it. apply-partial rejects connection payloads — manage connections through their dedicated /api/connections API instead.

Shared apply request fields

POST /api/config/setup/apply accepts: POST /api/config/setup/apply-file accepts multipart form field file (YAML or JSON) and optional query parameter dry_run (boolean, default false). It does not accept content or output_format in the body. POST /api/config/setup/apply-partial accepts: Partial apply strips optional metadata keys schema_version, name, and description before validation. The configuration root must be a mapping.

Partial apply restrictions

Schema sections that own a dedicated REST write surface cannot be applied through partial apply. Requests that include connection (nested or dotted keys such as connection.api_key) are rejected with a validation error directing callers to /api/connections instead. This prevents mixed-row storage and bypasses secret-migration and cache-invalidation hooks on the connections API.

Apply response shape

Both apply routes return ConfigApplyResponse with top-level success and a result object containing added, updated, deleted, skipped (integers), changes (array of path, old_value, new_value, action), and errors (string array).

Opaque dict schema fields

Schema fields declared with type: dict (currently connection and pricing.model_overrides) are stored as a single JSON blob in one APP_CONFIG row (KEY empty) rather than flattened into dotted path segments. Export, drift, and partial-apply merge operations preserve these dicts as opaque units. Connection changes must still go through /api/connections, not partial apply. Use partial apply or Advanced Settings for pricing.model_overrides updates.

Config sources endpoint

GET /api/config/setup/sources resolves where each configuration value comes from — the effective value and every layer in the priority chain. The Config Profiles Show Sources modal and Advanced Settings per-field source badges call this route. Requires access_config_panel.

GET /api/config/setup/sources query parameters

When neither parameter is supplied, Genesis resolves source information for every schema field (~640 paths). This is a database-intensive operation — prefer path or section when calling programmatically.

GET /api/config/setup/sources response fields

When path is set, the response is SingleValueSourceResponse: When path is omitted, the response is ConfigSourcesResponse with sources mapping each path to the same ValueSourceInfo shape (path, effective_value, source, all_sources).

Snowflake context endpoint

GET /api/config/snowflake/context backs Snowflake-aware setup helpers in the Config UI, including generated Snowsight approval links and Snowflake endpoint values shown for outbound-access setup.

GET /api/config/snowflake/context response fields

Snowflake Native App install status procedure

CORE.GET_INSTALL_STATUS() reports the Native App’s installation or restart progress from Snowflake service state and Genesis startup progress. Call it from a Snowflake worksheet using the installed application’s database name:
The procedure takes no arguments, returns one row, and is granted to the application’s APP_PUBLIC role. It does not wait for startup. Each call reads Snowflake privilege, reference, compute-pool, service, container, and endpoint state; a failed probe leaves its fields NULL rather than failing the call. Genesis publishes changed startup details to APP1.STARTUP_STATUS every 15 seconds after its metadata connection is available.

CORE.GET_INSTALL_STATUS() result fields

The ETA uses cold-install baselines of 270 seconds for pool provisioning, 150 seconds for image pulling, and 450 seconds for Genesis initialization. When Genesis has reported progress for the current container, that progress informs the initialization ETA. These are estimates, not startup time guarantees.

Database connections config endpoint

GET /api/config/db-connections backs the Data tab connection list and other read-only database-connection pickers. Unlike most Config routes, this endpoint accepts any one of access_config_panel, manage_all_connections, or view_connections via require_any_permission.

GET /api/config/db-connections response fields

Each item in data[] includes: The three access fields above are read-only here. They describe legacy CUST_DB_CONNECTIONS rows, where every connection access gate still enforces them; a connection stored by the Connections API carries none of them and controls access with scope and role_names instead — this API’s spelling of the RBAC role grant; roles_with_access is the connections_action spelling and is not an HTTP alias. Sending a deprecated access field back to POST /api/connections, PUT /api/connections/{type}/{name} or connections_action CREATE/UPDATE is refused rather than accepted and dropped. * on an allowlist is the one accepted value, because it restricts nobody — on an HTTP body, and inside connections_action’s locator_json. That exception does not cover a deprecated name passed as a top-level tool parameter: connections_action does not declare those names, so its unknown-parameter guard rejects the call before any of this applies, * included. owner_bot_id has no wildcard: omit it. Sending roles_with_access to either HTTP route is also refused, with 422 — it is the connections_action spelling and not a field of this API, and unknown fields are dropped here, so accepting it would answer success having applied no grant. Sensitive fields (allowed_bot_ids, allowed_user_ids, connection_string) are returned only when the caller has manage_all_connections or owns the connection (owner_user_id match). For the virtual System Snowflake row injected when native Snowflake metadata is enabled, sensitive fields require manage_all_connections.

Microsoft Teams OAuth provider bootstrap endpoints

These routes are mounted under /api/config/oauth_providers/microsoft_teams and back the shared-default UI in Config -> Advanced Settings -> OAuth Providers. They require access_config_panel.

Microsoft Teams OAuth provider fields

License management endpoints

These routes are mounted under /api/license/... and back the Config -> Activation page. They require access_config_panel. In bypass mode, GET /api/license/status can still return activated: true. When no real license is stored, Genesis returns a synthetic placeholder so clients can distinguish bypass mode from a genuinely activated key. When a real license is stored, the same endpoint surfaces the real license details while still reporting spcs_mode and enforcement_disabled.

GET /api/license/status response fields

POST /api/license/activate request and response fields

POST /api/license/deactivate response fields

GET /api/license/check response fields

Jira automation and service-worker endpoints

These routes are mounted under /api/config/jira/.... The base Jira config routes and Jira EAI helpers require access_config_panel. The shared Jira automation and Jira service-worker routes in this section accept either access_config_panel or manage_all_connections, and Genesis exposes them only when Jira automation rollout is enabled. When rollout is off, these automation and service-worker routes return 404 with the disabled-feature message instead of exposing partial setup state. The web client discovers that rollout through GET /api/config/frontend field jira_automation_enabled.

Jira automation and service-worker auth fields

Jira automation poll-now request field

Jira automation polling behavior

Scheduled Jira Ticket Poller runs and steady-state poll-now calls (when lookback_minutes is omitted) share the same cursor and throttle rules:
  • Poll cursor overlap: After the first successful poll, steady-state runs search from last_successful_poll_at minus 600 seconds (10 minutes). Genesis replays this wider window because Jira enhanced search is eventually consistent — a delayed issue can still appear after the successful-poll cursor advances. The first poll and manual poll-now runs with an explicit lookback_minutes use poll_lookback_minutes from the saved config instead.
  • Poll throttle: When lookback_minutes is omitted on poll-now, Genesis compares the later of last_poll_started_at and poller_last_run_at against poll_interval_seconds. A poll is allowed when elapsed time is at least poll_interval_seconds minus 2 seconds — managed interval Flows can start just before the nominal boundary, and without this allowance sub-second drift would skip every other scheduled poll. Skipped responses include next_poll_seconds with the remaining wait time.
  • Duplicate-trigger metadata refresh: When an issue still matches the poller but Genesis has already recorded the same trigger fingerprint, the issue is skipped for redispatch. If that issue already has an open Request row in the observation ledger and the Jira summary changed since the stored title, Genesis records a metadata_changed ledger event and updates the Request title without redispatching the bot. When no open Request exists for that issue key, Genesis skips the metadata refresh and does not create ledger activity from summary drift alone.

ServiceNow automation endpoints

These routes are mounted under /api/config/servicenow/... and require access_config_panel or manage_all_connections.

ServiceNow webhook endpoint

ServiceNow automation request fields

Linear automation endpoints

These routes are mounted under /api/config/linear/... and require access_config_panel or manage_all_connections. Saving automation settings with enabled: true requires a shared Linear api_key connection; personal connections are rejected. Each dispatched issue attempt is recorded durably with its own orchestration thread and idempotency identity. Re-queuing an issue starts a new attempt rather than reusing a terminal run from an earlier attempt. Coding runs that were dispatched before a process restart are reconciled during startup recovery; a manual poll-now pass handles issue pickup and dispatch, not that startup recovery step.

Linear automation request fields

Azure DevOps automation endpoints

These routes are mounted under /api/config/azure-devops/... and require access_config_panel or manage_all_connections. Saving automation settings with enabled: true requires a usable Azure DevOps connection and project.

Azure DevOps automation request fields

run_as_user_id is not a request field. When enabled is true, Genesis persists it from the authenticated caller and clears it when automation is disabled. Scheduled polls and poll-now pass that stored owner into connection resolution, orchestrator thread ownership (create new stable threads with user_id / user_email, or reassign existing azure-devops-<connection>-<work-item-id> threads to the stored owner and synchronize thread metadata), and kickoff send_message (user_context). The GET response exposes only run_as_configured (whether an owner is recorded).

Azure DevOps automation poll-now response fields

Ticket automation endpoints

Read-only routes backing the Requests page Activity view and the Config → Request sources Health panel. They aggregate the observation ledger and per-provider configuration. Routes are sync def handlers (FastAPI runs them on the thread pool). When the ledger store is unreachable, activity and health responses set ledger_degraded: true with ledger_degraded_reason instead of returning HTTP 500. Event detail objects are redacted to an operational allowlist (no prompt text or customer content). Timeline kind values include detected, claimed, claim_failed, dispatched, resumed, suppressed_duplicate, status_changed, metadata_changed, writeback, error, promoted, and follow_up. A metadata_changed event means provider-side metadata (for Jira polling, the issue summary) changed on an already-tracked open Request; Genesis does not emit it when the open-work-item lookup is empty. For Jira polling, a status_changed event is also emitted when the provider status is unchanged but its configured lifecycle mapping differs from the ledger. Its summary identifies the provider status as remapped and includes the mapped lifecycle state when one exists, allowing a newly configured terminal mapping to close an item that already recorded that provider status. For Slack and Teams health entries, intake is automatic or manual promotion according to ticket_automation.auto_track_chat_requests (defaults to true). The intake checklist step is labeled Automatic chat intake or Manual chat promotion with matching detail text. For Azure DevOps (provider: azure_devops) health entries, intake is polling. Checks report whether assignment automation is Enabled, whether its connection, project, and owner are Configured, and whether a Recent poll completed. A poller that has not completed within its freshness window is reported as stale. source_key is {organization}/{project} (lowercased). Activity rows use the same provider, source_key, and external_key (the Azure DevOps work item id) identity.

SCIM admin configuration endpoints

These routes back inbound SCIM setup in the Genesis Config panel and require access_config_panel. In a Databricks App, every route in this admin group and every /scim/v2 provisioning route returns HTTP 501 with error_type: "not_available_here" for the admin API, or a SCIM error response with status "501" for the provisioning API. The response detail explains that users and groups come from Databricks SSO and recommends managing access with Databricks groups and Genesis roles. An identity provider cannot reach SCIM through the App’s sign-in flow. GENESIS_SCIM_REQUIRE_PROVISIONED_USERS is ignored in a Databricks App, including for SCIM-deactivated users in a migrated metadata database; sign-in eligibility comes from the App’s CAN_USE permission. If the variable is set, startup logs one warning that it is ignored.

Okta directory sync settings endpoint

PUT /api/config/scim/okta-sync/settings requires the MANAGE_USERS permission. It saves the Okta connection settings and returns the refreshed directory sync status. Omit private_key to keep the stored key; the key itself is never returned. The response data contains the refreshed status, including these connection fields:

GET /api/config/scim/config response fields

SCIM admin mutation fields

Slack configuration and runtime endpoints

These routes are mounted under /api/slack/... and back the Slack Integration admin flow, saved-token connection testing, per-bot deployment status, and live Socket Mode diagnostics. Except for the OAuth callback at /api/slack/oauth/callback, the admin routes in this section require access_config_panel.

Slack path and request fields

POST /api/slack/test response fields

GET /api/slack/bot/{bot_id}/runtime response fields

If no live adapter exists for the bot, the route returns success: true, has_adapter: false, and a message indicating that the session may not be running. When diagnostic collection fails unexpectedly, the route returns success: false with message and error.

POST /api/slack/bot/{bot_id}/reconnect

When the reconnect request succeeds, Genesis returns success, bot_id, the normalized requested_reason, a low-level result object from the adapter, and a fresh runtime snapshot. If no live adapter exists, the route returns 404. If targeted reconnect is unsupported or the reconnect does not complete successfully, the route returns 400.

Microsoft Teams Bot Framework endpoints

These routes back the Bot Framework messaging surface, per-bot credential management in Agents → [bot] → Teams, and optional ingress diagnostics. POST /api/teams/messages/{bot_id} and GET /api/teams/health are public — Genesis validates the Microsoft-signed Bot Framework JWT inside the adapter (or accepts a relay-forwarded JWT in X-Teams-Bot-Authorization only after the configured relay Snowflake identity passes confinement). Per-bot credential routes require manage_agents; the confined relay identity is denied every credential path.

Microsoft Teams path and request fields

GET /api/teams/bot/{bot_id}/credentials and POST /api/teams/bot/{bot_id}/credentials response fields

Credential changes take effect on the next bot session reload or Genesis restart.

GET /api/teams/bot/{bot_id}/runtime response fields

Saving Teams credentials does not guarantee a live adapter — registration also requires teams_active, the server-wide teams.enabled switch, and a non-empty teams_app_id. This route answers whether the adapter is registered, whether Teams is enabled server-wide, and whether inbound activities are actually accepted. POST /api/teams/messages/{bot_id} refuses inbound traffic when teams.enabled is false even if an adapter is still registered, and when no adapter is registered. Both cases return 404. If the bot’s session has not been created since the server started, decision, reason, detail, and decided_at are null and message explains that no registration decision was recorded yet.

reason values and typical message text

Genesis never returns the Bot Framework client secret from this route.

POST /api/teams/messages/{bot_id} behavior

  • 202 — activity accepted for normal message, conversation-update, or installation-update activities.
  • 200 (or other invoke status) — Adaptive Card invoke activity; response body is the invoke payload.
  • 401 — Bot Framework JWT validation failed.
  • 403 — inbound serviceUrl host is not allowed (see teams.allowed_service_url_hosts in Advanced Settings).
  • 404 — no adapter registered for bot_id, or Teams is disabled server-wide (teams.enabled is false). The error text distinguishes the global kill-switch from an unknown bot.
  • 503 — transient failure reaching Microsoft’s signing metadata (JWKS); Bot Framework may retry.

Google Chat direct-message endpoint

POST /api/google-chat/events/{bot_id} receives Google Chat HTTP endpoint events for a bot. The route is public so Google Chat can deliver events, but Genesis requires a Google-signed Bearer token whose audience matches the configured Google Chat endpoint audience. Genesis accepts only verified Google Chat service tokens, direct messages from human users, and users whose Google Workspace domain ID matches the connection’s allowed domain ID. Other event types, non-DM spaces, and non-human users are ignored. The endpoint returns an empty acknowledgement for accepted, ignored, or successfully dispatched events that do not require a synchronous card update. Approval-card interactions return a Google Chat update response with actionResponse.type: "UPDATE_MESSAGE" and a replacement cardsV2 entry. The replacement keeps the approval message in the conversation while showing its result. Authentication, validation, missing-adapter, or transient delivery failures are reported by the API service boundary.

GET/POST /api/teams/ingress-canary response fields

When teams.ingress_canary_enabled is false, the route returns 404. When enabled, the response is value-redacted — header values are summarized by presence, length, and a sha256_prefix only.

Launchpad endpoints

Launchpad checklist and guided-start endpoints. They are authenticated-only with no RBAC permission gate, so brand-new non-admin users can load their checklist and guided-start cards on first login. When launchpad.checklist_enabled is false, the response still succeeds with data.items: [] and data.mandatory_complete: true.

GET /api/launchpad/checklist response fields

Checklist item fields (data.items[])

Completion rules match the operator table in Advanced Settings — Launchpad setup checklist. The encryption_key item is returned only when the in-app Snowflake setup applies and the user can manage secrets; see RBAC Practical Guide. Connection visibility uses the same predicates as the Connections API (user_can_see_connection).

POST /api/launchpad/guide

This authenticated endpoint uses the fast model to select the next card or handoff, and its calls are billed. A response can require a second completion if the model repeats a question already answered. See Cost accounting for its Cost & Usage classification. Requests are rate-limited per user to 20 per minute and 6 per 10 seconds. Request body: The response uses the standard {success, data} envelope. For a question, data.question contains id (string), text (string), multi_select (boolean, default false), and options (two to six option objects). Each option has id (string), label (string), and description (string, default "").

CLI login bootstrap

GET /api/config/cli-login returns the narrow, unauthenticated OAuth bootstrap contract used by genesis login for its loopback+PKCE browser flow. It returns only IdP coordinates and authentication state; it does not assemble the broader frontend runtime configuration.

GET /api/config/cli-login response fields

Frontend bootstrap config

GET /api/config/frontend is the lightweight bootstrap contract the web client uses for auth-safe runtime flags and chat UI limits.

GET /api/config/frontend response fields

Managed upgrades endpoints

The routes are mounted only when the managed deployment enables the panel flag; see Advanced Settings — Launchpad feature flags. For route access requirements, including the human-only write rule, see Genesis Agent Security Controls — Permissions. PUT /api/managed-upgrades/controls accepts expected_revision (integer or null for the first save), policy_mode (automatic-in-window, approval-required, approve-then-next-window, or by-version-class), paused (boolean), and window with iso_weekday (1–7), start_local (HH:MM), an IANA timezone, duration_seconds, and maximum_graceful_drain_seconds. It may also include pull_source_repository (an untagged OCI repository or null) and customer_scan (null or an object with result_deadline_seconds, from 1 to 30 days). The GET /api/managed-upgrades response includes agent_reported (false until the agent has pushed state since app startup), settings_rolled_back, agent_state (null or the latest controls_floor_generation, upgrade_state, and running_release), and controls (null or the saved document with its revision, generation, updated_by, and updated_at). A save returns those stored-control fields. Approval returns them under controls plus the approval_id the agent records when it consumes the approval. The approval body contains expected_revision and choice (upgrade-now or next-window). The scan-verdict body contains expected_revision, linux_amd64_digest (sha256: plus 64 lowercase hexadecimal characters), verdict (pass or fail), and optional reason (up to 200 characters; a failure needs a non-empty reason). The reason cannot contain control, format, or surrogate characters. This route returns the saved-control fields. The management agent uses two additional routes, mounted only when the feature flag is on and GENESIS_UPGRADE_CHECK_TOKEN is set. Both require that bearer token; hosted mode refuses /api/internal/ routes. The agent state body has schema_version: "internal-managed-upgrade-state-v1", controls_floor_generation, upgrade_state, and running_release. The agent pushes its latest state once per cycle; it is held in memory since app startup.

Hosted documentation session

POST /api/docs/session creates the short-lived Mintlify callback URL used when docs.use_internal_docs is false. The route accepts only a relative path in the hosted /docs tree and requires a genuinely authenticated human; delegated, machine, system, Snowflake, and auth-disabled callers are refused. For the configuration, secret-management, and hosted-docs rollout requirements, see Advanced Settings — Documentation settings.

POST /api/docs/session request

Example:

POST /api/docs/session response

The url is for immediate browser navigation and contains a ten-second Mintlify login JWT. The resulting hosted documentation session can remain valid for up to one hour. The endpoint returns 400 for an invalid redirect, 403 for an ineligible caller, and 503 when hosted mode or signing-key/session creation is unavailable.

Demo mode status

GET /api/config/demo-mode returns demo-mode status and the limits the web client needs to render budget and concurrency UI. The route is public (no RBAC permission required).

GET /api/config/demo-mode response fields

Hosted Genesis AI

Hosted Try Me instances configured to use their owner’s Genesis AI credit expose these endpoints for the dashboard’s Genesis AI status, activation, and credit meter. Configuration keys and defaults are in Advanced Settings. The status response also includes data.genesis_ai_only. It is true on hosted instances using their owner’s Genesis AI plan, even before the key is available or configured. In this mode, other AI providers cannot be configured, selected, tested, or called. On these pot-backed instances, startup removes AI provider configurations and their stored credentials that were baked into the golden image, including LiteLLM proxy, OpenAI, and Bedrock providers. This cleanup is enabled by demo.genesis_ai_from_pot, which defaults to false. The activation endpoint requires the owner’s Authorization: Bearer <access-token>. It returns the same success envelope with data.available, data.configured, and data.active booleans as the status endpoint. A completed activation is idempotent: if the Genesis AI key is already configured and its first issue completed, no provisioner request is made. Otherwise, activation makes one HTTPS request to the provisioner, which mints the owner’s key in five or six LiteLLM calls, then writes the instance’s LLM configuration. The provisioner rate-limits key issuance to 10 requests per minute. Before checking whether a key needs fetching, activation also removes any remaining baked-in providers and reloads the runtime if it removed any. The endpoint returns 404 when this is not a pot-backed hosted instance, 401 when the bearer token is missing, 403 when the caller is not the owner or has not completed the Try Me profile or email verification, and 503 when the provisioner cannot be reached or refuses the instance request, or when a baked-in provider cannot be removed; in that case Genesis AI is not configured. Grants are issued only to the instance’s recorded owner. The credit endpoint reads the same pot shared with the owner’s Genesis Desktop computers. It accepts the optional boolean query parameter fresh, which defaults to false: successful readings are reused for up to three minutes, while fresh=true reuses a reading only while it is younger than 15 seconds. When Genesis AI is not active or the provisioner cannot provide a reading, data.available is false. The response’s data uses the same credit fields as GET /api/desktop/credit. In demo mode, GET /api/demo/user-info-status also reports has_submitted: true for the instance owner when Try Me already collected and pushed the owner’s profile with the owner record, so the in-instance profile prompt is not shown again.

GET /api/internal/demo-budget/aggregate (provisioner poll)

Cold-provisioned Try Me instances expose this internal route so the provisioner can read deployment-wide spend vs cap and drive auto-destroy when the budget is exhausted, and read the instance’s latest LLM call and user sign-in to decide when an idle free-tier instance may sleep. It is separate from per-agent RBAC budgets under /api/budgets/.... Authentication: Send X-Genesis-Internal-Token with an eligible demo provisioner token (see demo access gate settings) or a valid internal service token. Demo provisioner tokens work only in demo mode and only for /api/internal/* route dependencies; they are not user credentials for other API paths. These routes do not use the Bearer token required by other /api/internal/... flow routes. Response fields: Each poll also performs two activity reads in addition to the budget read. The LLM-call timestamp comes from the metrics service’s LLM_CALLS store and may lag a call by up to one metrics export interval. The user timestamp comes from the users table. An activity-read failure returns null for that field without failing the budget response.

PUT /api/internal/demo-budget/cap

Change the effective demo budget cap for the running instance. The change is applied immediately without a process restart; aggregate spend is not read by this operation, and the next budget enforcement refresh uses the new cap. Authentication: Uses the same X-Genesis-Internal-Token authentication as GET /api/internal/demo-budget/aggregate: the header must match demo.instance_auth_token, the eligible legacy demo.internal_auth_token, or a valid internal service token. An Authorization: Bearer token or session cookie is not accepted for this route. Request body: Response fields: Example — set a USD 250 cap:
Example response:

Provisioner owner and state routes

Hosted Try Me provisioners use these internal endpoints to register an instance owner and export its state. They use the internal-token authentication described above and do not accept a Bearer token or session cookie in its place.
PUT /api/internal/demo-access/owner
The provisioner calls this at claim to record the owner. Repeating the same owner is idempotent. A different existing owner produces 409 unless the request sets force for an administrator reassignment. email must be a non-empty verified owner email; sub is the owner’s Auth0 subject and may be empty for an email-based administrator reassignment. Example:
Response fields: owner_sub, owner_email, owner_display_name (strings), and changed (boolean). Returns 401 for a missing or invalid internal token, 409 if another owner is recorded and force is false, and 200 on success.
POST /api/internal/demo-state/export-state
Streams the same raw, full-fidelity application/x-tar archive documented for POST /api/admin/export-state. It includes Cache-Control: no-store, private and Pragma: no-cache headers. No request body or query parameters are required.
POST /api/internal/demo-state/export-content
Streams a human-readable export in the selected format. The artifact is not cached (Cache-Control: no-store, private and Pragma: no-cache).
POST /api/internal/demo-state/export-microapps
Streams a microapp workspace archive as application/gzip with Cache-Control: no-store, private and Pragma: no-cache headers.

Presenter Controls endpoints

These routes backed the Presenter Controls panel, which is no longer in the web UI, and remain available when features.presenter_controls_enabled is true. They prepare, reset, and clean up Sales Engineering vignettes scoped to one browser-local presenter_session_id and the authenticated caller. All routes require the capability plus Genesis Staff (is_staff) or a valid X-Genesis-Internal-Token. The process-local internal token works on any enabled instance; the shared demo token remains limited to demo mode. Shared request field:

Reusable demo versions (/api/demo/versions)

These routes back the Demo version controls in each Presenter Controls scenario card. They list shared ready revisions and the caller’s private drafts, prepare a tailored revision once, create isolated presentation runs, and retain run history. They use the same presenter-operator authorization as the other Presenter Controls routes. POST /api/demo/versions body fields: POST /api/demo/versions/runs body fields: The catalog response exposes each version’s status (draft, validating, ready, or failed), visibility (creator or environment), preview and compiled configuration, optional onboarding bundle metadata, and runs with recommended_gold, latest, and complete history. Presentation runs use prepared, running, completed, failed, or incomplete statuses. When this catalog is listed, Genesis reconciles nonterminal client-file onboarding runs against their durable Mission status before returning the catalog, so a refreshed Presenter Controls view can reflect progress from a run that continued after the original request.

Proactive pipeline recovery (/api/demo/rca)

GET /api/demo/rca/status returns data.investigation_thread for the current Slack investigation when the delivered notification includes both a target bot ID and a Slack root message timestamp. The field is null when either value is missing; otherwise it includes the bot ID, the Slack root timestamp as the Genesis thread ID, and a URL-encoded relative href to the investigation chat. POST /api/demo/rca/reset accepts these optional body fields in addition to presenter_session_id and source_type: POST /api/demo/rca/fresh-session accepts this body: The endpoint releases only the authenticated presenter’s terminal dbt Cloud ownership. The run must no longer be active, its provider status must be a terminal error, Slack failure delivery must be complete, and any recovery run must be terminal. It returns success: true with data containing superseded_scope_ids, incomplete_run_ids, and retryable_fixture_count. For ticket_dashboard_rca, a ticketed dashboard fixture is changed to retryable and its active Jira issue pointer is cleared so the new session can retry preparation; presentation runs affected by the supersession are marked incomplete.

Interactive AUM dashboard (/api/demo/aum)

Cost optimization (/api/demo/optimization)

Presenter Control Missions (/api/demo/presenter-missions)

Tracks exact caller-owned Missions for disposable blueprint vignettes.

Ticket-driven dashboard anomaly (/api/demo/ticket-rca)

Orchestrates the Jira ticket-driven RCA presenter vignette. Genesis creates a real Jira issue tied to a caller-owned demo_dashboard_root_cause Mission, attaches a dashboard screenshot, starts the selected ETL failure fixture, and records Work-item linkage when Jira automation picks up the ticket. The issue is left unassigned after preparation; assign it to one of the configured assignment_targets to trigger Genesis pickup. When the dashboard RCA work is completed, Genesis posts a Jira comment beginning with “Fixed.” It includes the recorded root cause when available, a link to the RCA report, and a Prover result link to the report’s #prover-evidence section. Shared request field (query on GET, body on POST): POST /api/demo/ticket-rca/prepare body fields: POST /api/demo/ticket-rca/reset body fields: The two demo-version fields must be supplied together. The reset operation validates that the revision, run, presenter session, and scenario still match; the prepare operation then uses that isolated fixture when it creates the Jira issue. Presenter RCA pickups are serialized per presenter scope while an earlier ticket still owns the active fixture in ticketed state. A later assigned ticket is retried on a subsequent polling pass after that fixture leaves ticketed; completed example issues do not block a new RCA pickup. GET /api/demo/ticket-rca/status response data fields: When present, active_issue.work also includes the persisted Jira provider_status. During status reconciliation, Genesis maps that provider status with the current Jira lifecycle configuration; mapped done, needs_info, or working states replace a stale work state and are recorded as a provider_status_observed event. An issue is reported as completed only when its current Jira status maps to done and the durable Work item contains the exact agent writeback receipt for the dashboard_rca_debrief comment. The receipt includes issue_key, work_item_id, thread_id, receipt_id, comment_sha256, and terminal event metadata; a missing or mismatched receipt leaves the fixture in its nonterminal state even when Jira is already done. When a retained legacy fixture has no dbt_cloud_access_url, reconciliation can still complete it after the linked Jira Work reaches done or released. It uses the fixture’s jira_issue_key and then marks the fixture completed while releasing that exact issue pointer. Prepare failures return success: false with error.code such as JIRA_SETUP_REQUIRED, JIRA_ISSUE_TYPE_UNAVAILABLE, CODING_AGENT_UNSUPPORTED, or DBT_PROVIDER_BUSY when an earlier presenter run still owns the shared dbt Cloud job. Version-bound resets can also return DEMO_VERSION_BINDING_INVALID or DEMO_VERSION_PROFILE_UNAVAILABLE.

Auth and user runtime config endpoints

These routes back lightweight auth bootstrap and the persisted background-job settings used by Pulse and Digital Twin.

GET /api/config/auth response fields

GET /api/config/user-config response fields

GET /api/config/user-config returns success and config.

config fields for /api/config/user-config

POST /api/config/user-config

Send a JSON body shaped as { "config": { ... } } using the same field names listed above. The response returns success and message.

Branding endpoints

These routes back Config → Branding. See Branding for the customer-facing workflow. logo_type is square, wide, header_light, or header_dark. The header_light and header_dark types are optional overrides for the main Genesis header and are not copied into generated app workspaces.

GET /api/config/branding and PUT /api/config/branding fields

GET /api/config/branding returns success and data. PUT /api/config/branding accepts any subset of the writable fields below; omitted fields are left unchanged. Logo URL fields are read-only on PUT. Each non-null URL includes a ?v={version} query parameter that changes when the underlying file is replaced, so clients can cache the image until the version changes. POST /api/config/branding/logo/{logo_type} returns success and logo_url with the uploaded file’s URL (including the version query parameter).
The one-shot startup storage-cleanup audit is separate from these persisted job flags. It is controlled only by environment variables:
  • GENESIS_STORAGE_CLEANUP_STARTUP_AUDIT defaults to false. Set it to true or another truthy value such as 1, yes, or on to schedule the startup dry-run audit.
  • GENESIS_STORAGE_CLEANUP_STARTUP_AUDIT_DELAY_S defaults to 30 and controls the startup-audit delay in seconds.
When enabled, Genesis writes a dry-run reclaim report under storage_cleanup_reports/, including a timestamped startup-dry-run-<timestamp>.json file and latest.json. The audit does not delete files.

LLM provider create duplicate guard

POST routes that create an LLM provider configuration run a pre-write duplicate check before persisting submitted credentials, endpoint metadata, or process environment state. Affected routes include:
  • POST /api/config/llm-providers/openai
  • POST /api/config/llm-providers/cortex
  • POST /api/config/llm-providers/databricks
  • POST /api/config/llm-providers/litellm-proxy
  • POST /api/config/llm-providers/other (for example Bedrock, Anthropic, Google Gemini)
When the provider is already configured, Genesis returns LlmOperationResponse with success: false and does not write the submitted API key or other credentials to the secret store, mutate process-global LiteLLM environment variables, delete backing connections, or run model discovery. The response message is: Configuration for {display_name} already exists. Use configure endpoint to update and enable it. where {display_name} is the provider’s human-readable title (for example OpenAI, Databricks, LiteLLM Proxy). Use the matching provider PUT route — for example PUT /api/config/llm-providers/openai — to update an existing configuration instead of calling POST again. If the duplicate check itself fails (for example the existence probe raises), Genesis fails open and proceeds with the create so the route does not introduce a new 500 where it previously returned 200.

LLM provider save validation and rollback

Provider create and update routes that persist credentials run under a rollback guard. Genesis snapshots the provider’s stored configuration (managed secrets, APP_CONFIG paths, and any process env keys the validation step may mutate). When a save has an applicable probe target, Genesis runs the save-time connection test when test_connection is true; it also tests a submitted secret value that differs from a credential already configured for that provider, even when test_connection is false. Process env (for example OPENAI_API_KEY or LITELLM_PROXY_*) is committed only after validation passes. The replacement check applies when a provider already has a stored credential; initial setup with no stored credential, re-sending an unchanged value, and submitting a secret reference do not trigger an automatic test. An Azure OpenAI connection-only save without a deployment has no probe target and remains untested. When validation fails, Genesis restores the snapshot and answers with LlmOperationResponse where success is false and message carries the validation error directly (for example Connection test failed: ...) — not a generic Failed to save configuration: ... wrapper. The prior working credential remains in the secret store and runtime caches are invalidated so the restored value is served on the next read. Bedrock is admitted differently: when test_connection is true, Genesis tests the submitted candidate before writing it. A rejected Bedrock candidate leaves the existing configuration untouched and does not require a rollback; the response still reports success: false with the connection-test message. After a successful provider save, model-assignment save, or provider credential mutation through the secret-fields API, Genesis publishes the change to the running process-local LLM runtime. The next operation re-resolves provider credentials and model assignments; a server restart is not required. Failed guarded saves and rollbacks do not publish a runtime change. Successful provider saves that persist an embedding model preference may also include optional embedding_adoption on LlmOperationResponse — the first-save-wins adoption outcome when no global embedding identity exists yet. See First-save embedding adoption. If rollback itself fails, message appends Previous credentials could not be restored automatically; please re-enter them. For LiteLLM Proxy, a final step runs after validation succeeds under the same save lock: it syncs the shared OAuth backing connection (litellm_proxy.shared when auth_mechanism is oauth) or deletes that backing connection for other auth modes, then sets providers.litellm_proxy.admission to auto and invalidates LLM runtime caches. These side effects are not covered by the AppConfig snapshot and are not rolled back — they run through finalize_without_rollback on the provider write guard. Affected routes include POST and PUT on /api/config/llm-providers/openai, /cortex, /databricks, /azure, /litellm-proxy, and /other (Bedrock, Anthropic, Gemini, and other LiteLLM-backed providers). LiteLLM Other Providers defer commit_env until after test_connection succeeds so invalid keys are never published to OPENAI_API_KEY, ANTHROPIC_API_KEY, or related env vars. For AWS Bedrock behind a custom endpoint (endpoint_mode: "custom"), the first POST /api/config/llm-providers/other create deliberately skips the save-time connection test even when test_connection is true because there is no reachable probe target on that path; use POST /api/config/bedrock/test-connection or retry with PUT once the configuration exists (updates always honor test_connection).

Azure OpenAI connection test API version

POST /api/config/azure/test-connection accepts an optional api_version. When the field is omitted or null, Genesis uses the saved providers.azureopenai.connection.api_version; if none is saved, it uses 2025-04-01-preview. Set api_version explicitly to test a different Azure API version without changing the saved provider configuration. Provider saves also default providers.azureopenai.connection.api_version to 2025-04-01-preview when no version is supplied. See Azure OpenAI provider setup.

Cortex provider configuration endpoints

These routes back the Snowflake Cortex card on Config -> AI Providers. In Snowflake-native runtimes where Cortex is already available, POST and PUT /api/config/llm-providers/cortex can omit credential fields. In other deployments, provide either PAT credentials (account_url and api_key) or key-pair credentials (snowflake_account, snowflake_user, and private_key). Set auth_method to "pat" or "keypair" to make the mode explicit; when omitted, Genesis infers key-pair mode when private_key and snowflake_account are present without api_key. On Snowflake Native App / SPCS hosts, Genesis registers Cortex whenever it is available but does not overwrite an existing llm.active_provider. Saving or testing Cortex adds or updates the cortex provider row; activation happens only when no chat provider is active yet unless the request sets force_activation: true or the operator explicitly switches the active provider. POST and PUT with test_connection: true validate the submitted api_key and account_url when present. On Cortex-active installs where activation exports the working PAT into CORTEX_API_KEY, Genesis does not re-validate that exported credential instead of the PAT being saved — a rejected submission fails and the rollback guard restores the prior working key.

Shared Cortex request fields

GET /api/config/llm-providers/cortex response fields

Databricks provider configuration endpoints

These routes back the Databricks card on Config -> AI Providers. POST /api/config/llm-providers/databricks defaults auth_mode to pat. In PAT mode, llm_endpoint and api_key are required on create. In OAuth mode, oauth_connection_name is required, llm_endpoint is optional, and Genesis derives https://<connection-host>/serving-endpoints from the selected connection when the field is omitted. Runtime and OAuth-backed test flows resolve the acting user’s OAuth grant from that saved connection instead of persisting a user access token on the provider. Headless calls cannot use a per-user grant: Databricks embeddings, embedding adoption, and embedding-model save/activation require either PAT credentials plus a workspace URL, or OAuth mode with a resolvable service_principal M2M connection configured in genesis-config.yaml at providers.databricks.connection.oauth.m2m_connection_name (not exposed on these REST routes). U2M-only OAuth remains valid for user-present chat and test flows, but it does not make Databricks ready for embeddings. app_identity is deployment-selected for Databricks Apps, not a credential supplied by these routes: with providers.databricks.connection.auth_mode=app_identity, runtime and connection-test calls use the App service principal token, and provider settings saves are refused so a PAT or OAuth credential cannot replace it. The App runtime must supply DATABRICKS_HOST, DATABRICKS_CLIENT_ID, and DATABRICKS_CLIENT_SECRET; calls fail without them and do not fall back to PAT or user OAuth credentials. Genesis routes calls through LiteLLM with per-call api_key and api_base, not ambient DATABRICKS_* variables. See Databricks App identity setup.

Databricks configuration request fields

GET /api/config/llm-providers/databricks response fields

Databricks connection test request fields

If POST /api/config/databricks/test-connection receives neither PAT fields nor OAuth fields, Genesis tests the saved Databricks configuration. When the saved config uses OAuth, Genesis resolves the current user’s grant for the saved oauth_connection_name.

Bedrock provider configuration endpoints

These routes back the AWS Bedrock card on Config -> AI Providers. For POST and PUT /api/config/llm-providers/other, an omitted auth_mode, endpoint_mode or bedrock_chat_route_mode keeps the effective value — the stored one, or the schema default (api_key, default, invoke) when nothing is stored. When auth_mode is api_key, aws_bearer_token is required. When endpoint_mode is default, aws_region is required. When endpoint_mode is custom, custom_endpoint_url is required. Each requirement is evaluated against the effective configuration the write would produce — the submitted fields over the stored values, deployment environment inputs and schema defaults — so a value already in effect satisfies it without being resent. Only the submitted fields are written; an inherited value is used for the check and the connection test but is not copied into the stored configuration. Custom endpoints support api_key authentication only; auth_mode: "iam_role" with endpoint_mode: "custom" is rejected before the configuration is saved. When you save a Bedrock custom-endpoint configuration, Genesis also syncs the runtime options into App Config under providers.litellm_bedrock.settings.*. GET /api/config/bedrock/iam-status accepts region and refresh query parameters. region defaults to us-east-1, and refresh defaults to false.

GET /api/config/bedrock/iam-status response fields

Bedrock configuration request fields

GET /api/config/llm-providers/other/bedrock returns the saved auth_mode, endpoint_mode, custom_endpoint_url, custom_auth_header, strip_regional_prefix, fake_stream, extra_headers_json, verify_ssl, and masked credentials, and always includes the effective bedrock_chat_route_mode value. The saved provider config also keeps bedrock_chat_route_mode, so later Bedrock test requests can omit it and reuse the current setting. The synced App Config keys are providers.litellm_bedrock.settings.strip_regional_prefix, providers.litellm_bedrock.settings.fake_stream, providers.litellm_bedrock.settings.extra_headers, and providers.litellm_bedrock.settings.verify_ssl.

Bedrock connection test request fields

When a Bedrock test request omits a field or sends it as null, Genesis resolves the effective parameters from the saved Bedrock configuration first, then falls back to documented defaults such as aws_region: "us-east-1", verify_ssl: true, and bedrock_chat_route_mode: "invoke". For test requests, send either an AWS bearer token or a complete access-key/secret-key pair, not both. A partial key pair or conflicting credentials is rejected. If auth_mode is omitted, explicit credentials in the request select api_key mode; requests using iam_role must not include explicit credentials. Access-key authentication requires the default Bedrock endpoint. For custom endpoints, POST /api/config/bedrock/test-connection sends a lightweight probe that matches bedrock_chat_route_mode: invoke targets /model/{modelId}/invoke with an Anthropic-style body, while converse targets /model/{modelId}/converse with the Bedrock Converse body. When a Bedrock model is already configured, Genesis probes that model; otherwise it uses a dummy model ID. When auth_mode is iam_role and endpoint_mode is custom, the test route returns success: false and instructs callers to use API-key auth for custom proxies. When custom_auth_header is a non-bearer header such as x-api-key, Genesis adds a structurally valid SigV4 Authorization header plus the selected custom header so API Gateway-based Bedrock proxies can validate the request. A custom proxy that returns 403 for the lightweight test model is reported as success: true with details.warning and details.status_code so callers can surface the warning while still treating the endpoint as reachable.

Bedrock connection test response fields

Gemini connection test request fields

POST /api/config/gemini/test-connection tests Google Gemini through LiteLLM. The request can use a submitted API key or the saved Gemini credential. The AI Providers test flow supplies the selected model when available, preferring the fast_model assignment, then default_chat, then the first model in the test list. This prevents a valid credential from failing because a different unsupported fallback model was probed.

Genesis AI connection test

The test does not save or change configuration. It makes one or two HTTPS requests to Genesis AI: a model listing and, when applicable, a one-token message probe. Each request can take up to 10 seconds. The response uses success and message, with optional details fields:

Genesis Desktop embedding test restriction

POST /api/config/llm-providers/embedding-config/test accepts only Genesis AI on Genesis Desktop. A request for another provider returns 403; if the provider is omitted and inferred from the model, the inferred provider is checked as well.

AI Providers page overview endpoints

These routes back the Beta AI Providers page (/config/ai-providers/). They are read-only and available to any authenticated user. Responses expose masked connection subtitles in extraInfo — never secret plaintext.

GET /api/config/ai-providers/overview response fields

Top-level response: summary object: Each providers[] item:

GET /api/config/ai-providers/embedding-health response fields

This endpoint carries the verdict already maintained by Digital Twin rather than inferring health from recorded provider configuration. The request can take up to 5 seconds while Genesis obtains the verdict from Twin. A slow or unavailable Twin does not become a false healthy or failed result; the endpoint returns status: "unavailable" instead.

Mark provider configured endpoint

Secret-only providers (openai, anthropic, gemini) have no non-secret configuration PUT step. Saving their API key through the secret fields API already runs the common save finalize: it sets providers.<namespace>.admission to auto and, when no chat provider is selected and exactly one eligible provider remains, selects it. The Beta wizard still calls this endpoint once after the Secrets step succeeds; it re-admits the provider when its parameters are sufficient and adopts it for embeddings when no embedding pair is saved. Request body: Response (LlmOperationResponse): On Genesis Desktop, this endpoint accepts only the Genesis AI provider; requests for other providers return 403.

Ensure provider endpoints endpoint

Secret-only providers (openai, anthropic, gemini) persist credentials through the generic secret-fields API rather than a full provider-save PUT, so they skip the CUSTOM_ENDPOINTS registration those PUT routes perform as a side effect. The Beta wizard calls this endpoint once after the Secrets step succeeds and before the SPCS Approve External Access step, so there are egress hosts to approve.

Secret schema field endpoints

These routes back Config → Secrets and Advanced Settings secret-field editors. Plaintext values are stored in the managed secret store; references (secret_*, env_*, aws_*, and related forms) are stored in APP_CONFIG.

POST /api/config/secret-fields/{field_path}/set-value provider credential guard

When field_path maps to a provider credential (for example providers.openai.connection.api_key) and the field already resolves to a configured credential, Genesis runs the same connection test used by the matching provider save before accepting the overwrite. First-time writes skip the guard because there is no prior credential to restore. On validation failure, the previous credential remains stored. On success, the response follows SetSecretValueResponse with success: true. Full provider-save routes also call the same registration helper, but treat failures as best-effort (they do not fail the whole config save). Callers whose sole purpose is endpoint registration should check the response success field instead of assuming registration succeeded. Path parameter: Response (LlmOperationResponse):

Provider admission endpoint

PUT /api/config/llm-providers/{provider}/admission changes the operator’s admission decision without deleting the provider connection or its embedding identity. Use deny to refuse provider use, or auto to permit use when its parameters are sufficient. The operation requires access_config_panel. The response uses LlmOperationResponse with success and message fields.

LLM provider activation endpoint

POST /api/config/llm-providers/{llm_type}/activate switches the active chat provider without rewriting that provider’s saved model assignments. The Beta AI Providers page uses this route for Set Active on the Chat tab instead of PUT /api/config/models/main/assignments with use_defaults: true, which would reset tier overrides back to Genesis defaults. Request body (LLMActivateRequest): Response (LlmOperationResponse): When activation succeeds, Genesis runs an AI-provider environment sync that applies the active provider’s saved Change Models tier assignments and clears stale LiteLLM process globals (LITELLM_MODEL, LITELLM_FAST_MODEL, LITELLM_PREMIER_MODEL, and the LiteLLM SDK api_base override) when switching between LiteLLM-engine providers (azureopenai, databricks, litellm_*, including litellm_proxy). It does not delete LITELLM_PROXY_* bootstrap env vars. For composite providers (litellm_*, including litellm_proxy), activation uses the saved provider configuration and does not persist llm_endpoint from the activation request. LiteLLM Proxy runtime calls resolve proxy_api_base from providers.litellm_proxy.connection.base_url in AppConfig on each request, so switching to or from litellm_proxy does not require a server restart. Runtime chat resolves main, fast, and frontier models from llm.active_provider assignments on each call.

LLM providers overview endpoint

GET /api/config/llm-providers/info backs the Stable AI Providers overview grid on Config -> AI Providers (/config/llm-providers/). It returns enhanced provider rows plus a summary of configured, effective, and active main and embedding providers. Each provider object in data includes: If the runtime LLM state is unavailable, this endpoint still returns success: true with the stored provider rows enhanced for the response. Those rows retain configured: true, but report available: false, effective: false, and is_fallback: false because runtime availability and effectiveness could not be confirmed. Stored rows without llm_type are omitted, matching the healthy response path. When usable LiteLLM Proxy settings are loaded from genesis-config.yaml or legacy LITELLM_PROXY_* environment variables (and proxy_api_base plus the selected auth_mechanism credentials are complete), Genesis adds or updates a litellm_proxy row in data and may omit the generic litellm row so Helm-managed proxy deployments surface LiteLLM Proxy instead of Other Providers. For those deployments, summary.active_main_provider can report litellm_proxy while summary.configured_provider and summary.effective_provider remain litellm. When LiteLLM Proxy runtime configuration is incomplete or temporarily unavailable, the litellm_proxy row reports configured: false, available: false, and empty key_preview / llm_endpoint, and summary fields that would route chat through the proxy (active_main_provider, effective_provider, main_model_assignments, fallback_provider, and embedding selections on the proxy) are cleared so the overview does not show a stale active proxy. The exception is a concrete non-fallback litellm_<vendor> active provider (for example, litellm_bedrock or litellm_anthropic): its default remains in the summary even though effective_provider is normalized to litellm. If runtime config resolution fails, the response may include message: "LiteLLM Proxy runtime configuration is temporarily unavailable". The top-level summary object also includes:
On Snowflake Native App / SPCS hosts, summary.configured_provider reflects llm.active_provider and wins over CORTEX_MODE. Genesis defaults chat to Cortex only when configured_provider is unset and Cortex is available. CORTEX_OVERRIDE no longer affects these state reads (legacy execution layers may still honor it until their cleanup). Provider selection is APP_CONFIG state written through the config API, Config -> AI Providers, or a mounted genesis-config.yaml; schema-bound environment defaults only seed unset fields. Startup migration aa011_cortex_active_override_heal restores a pre-fix install whose active provider was incorrectly forced to cortex when legacy EXT_SERVICE_CONFIG.model_config.main_provider still records the operator’s original choice. After startup migrations, Genesis invalidates the AppConfig snapshot, LLM runtime cache, and this overview cache so the response reflects the post-migration active provider on the same boot.

LiteLLM Proxy provider configuration endpoints

These routes back the LiteLLM Proxy card on Config -> AI Providers. When test_connection is true or model is omitted, Genesis calls the proxy /models endpoint to validate the connection and discover models. Genesis requests {proxy_api_base}/models directly; when the proxy responds with HTTP 301, 302, 303, 307, or 308, Genesis follows up to two same-origin redirects whose target path ends in /models or /models/. Cross-origin redirects and redirects to non-/models paths are refused so authentication headers are not forwarded to another host. Genesis accepts the standard OpenAI-compatible response shape {"data": [{"id": "..."}]}, a bare array of model objects such as [{"id": "..."}], or the temporary Ally-compatible response shape {"data": {"Items": [{"model_id": "..."}]}}. For model discovery, Genesis uses model_type when the proxy returns it. model_type: "embedding" places the model in the embeddings list, model_type: "chat" places it in the chat list, and providers that omit model_type fall back to the historical model-name heuristic. Discovery responses may include transport prefixes such as proxy/ or litellm_proxy/; Genesis strips those prefixes before persisting catalog entries, Model Universe technical names, and saved assignments, then re-applies litellm_proxy/ only when applying runtime environment variables for LiteLLM calls. LiteLLM Proxy sentinel permission ids such as all-team-models, all-proxy-models, and no-default-models are excluded from picker and universe technical-name lists; allow-all sentinels can still expand discovery against the /v2/model/info catalog when the proxy does not return explicit model ids. On a successful POST, or on a PUT with initialize_models: true or a non-empty model, Genesis runs enriched discovery (/models permissions intersected with /v2/model/info metadata when available), upserts the result into Model Universe with source_type: "discovered", and infers default tier assignments from alias plus underlying model names. An ordinary PUT that changes configuration or credentials is assignment-neutral. When persisting inferred assignments, Genesis preserves any tier the operator already pinned with assignment_sources[task_type] == "override" (set via PUT /api/config/models/main/assignments or the Change Models UI). Override tiers keep their persisted model ids across proxy credential rotation, partial first save, or other re-save that re-runs catalog inference; freshly inferred tiers are saved with assignment_sources: "default". Materialization failures are logged but do not block the config save. When the /models fetch itself fails (network error, HTTP error, or invalid response), Genesis skips Model Universe materialization on that attempt and preserves any previously materialized discovered rows rather than reconciling against an empty failed fetch. When discovery is complete, Genesis reconciles stale discovered rows that are no longer advertised; custom rows are never overwritten. When no chat provider is explicitly active in APP_CONFIG (active_provider_raw is null — the Snowflake Cortex zero-config default does not count), Genesis auto-activates LiteLLM Proxy on create, or on an assignment-intent update, only when discovery yields usable tier assignments or the request includes a non-empty model. A failed or empty discovery with no model saves credentials but leaves the provider inactive and omits the GenesisApp reload. On Snowflake Native App / SPCS, the first save often runs with test_connection: false while External Access is pending; when discovery fails with no assignments and no model, the response includes an SPCS-specific warning_message directing the operator to Approve External Access and use Continue setup for the provider. A later PUT with initialize_models: true after EAI approval can activate the proxy when models become reachable. Regardless of auto-activation, a successful save always publishes providers.litellm_proxy.admission: auto in the finalize step described under LLM provider save validation and rollback, so POST /api/config/llm-providers/{llm_type}/activate and the New connection wizard Done step can switch the provider active without a separate POST /api/config/llm-providers/mark-configured call. When auth_mechanism is oauth and the effective oauth_credential_policy is per_user_oauth, Genesis skips admin-side service-account /models discovery on save and test; runtime calls use each user’s browser OAuth grant. Genesis resolves the effective policy by merging the request payload, genesis-config.yaml (providers.litellm_proxy.settings.oauth_credential_policy, providers.litellm_proxy.settings.allow_user_oauth_fallback_for_non_interactive), saved metadata, and LITELLM_PROXY_OAUTH_CREDENTIAL_POLICY / LITELLM_PROXY_ALLOW_USER_OAUTH_FALLBACK_FOR_NON_INTERACTIVE (explicit env vars take precedence over file-backed values). When auth_mechanism is oauth but the OAuth client configuration is incomplete, or when auth_mechanism is static_headers but static_headers_json is missing, invalid JSON, not a JSON object, or an empty object, Genesis falls back to API-key discovery headers when proxy_api_key or LITELLM_PROXY_API_KEY is configured; otherwise model discovery is skipped for that save or test request. In service_account mode, Genesis resolves oauth_custom_header_descriptors from the merged provider metadata first and, when that list normalizes to empty, from oauth_custom_header_descriptors on the shared backing LiteLLM Proxy connection (litellm_proxy.shared). It applies the effective descriptor list to the OAuth token request (applies_to token_exchange), forwards downstream headers during client-credentials token acquisition, and sends downstream headers on proxy /models discovery, background model-catalog loading, and runtime proxy calls.
Helm-managed deployments can bootstrap the same provider settings from genesis-config.yaml (providers.litellm_proxy, providers.litellm_proxy.settings.oauth_credential_policy, and related fields). Genesis merges file-backed values with saved metadata and legacy LITELLM_PROXY_* environment variables at runtime, including LITELLM_PROXY_TRANSPORT, LITELLM_PROXY_OAUTH_CREDENTIAL_POLICY, LITELLM_PROXY_ALLOW_USER_OAUTH_FALLBACK_FOR_NON_INTERACTIVE, and LITELLM_PROXY_OAUTH_CUSTOM_HEADER_DESCRIPTORS_JSON.

LiteLLM Proxy configuration request fields

On PUT, omitting auth_mechanism, transport, oauth_token_endpoint_auth_method, or allow_user_oauth_fallback_for_non_interactive preserves its stored value. Send a value explicitly to update it; schema defaults apply when no value is stored. For OAuth saves, Genesis checks secret references in backing-connection header values during prevalidation. Required headers used by service-account policies must have a value and resolve; unresolved required references reject the save. An optional unresolved reference is omitted from the shared backing connection, while its App Config reference remains available for later runtime resolution. The backing-connection writer checks references again during registration, so a secret that disappears after prevalidation can still cause a save failure after provider settings are committed; restore the secret and retry.

LiteLLM Proxy configuration response fields

GET /api/config/llm-providers/other/litellm-proxy also returns oauth_service_account_header_issue as a string or null. When a saved shared service-account configuration cannot send an OAuth header, the field explains the descriptor or missing required value that needs attention; it does not expose header values. The AI Providers status panel displays this issue. Before returning, GET /api/config/llm-providers/other/litellm-proxy merges safe runtime LiteLLM Proxy settings from genesis-config.yaml and legacy LITELLM_PROXY_* environment variables into the saved metadata payload (non-secret display fields such as proxy_api_base, auth_mechanism, transport, OAuth URLs and client ID, oauth_credential_policy, allow_user_oauth_fallback_for_non_interactive, oauth_custom_header_descriptors, and model; masked markers for configured secrets; oauth_service_account_header_value_keys derived from runtime providers.litellm_proxy.connection.oauth.service_account_header_values when present, otherwise saved oauth_service_account_header_values_json). When the merged runtime config is complete for the selected auth_mechanism, Genesis may set configured: true even when UI metadata alone would not. When runtime config resolution fails, Genesis sets configured: false and configuration_status: "unavailable" instead of returning stale saved fields. The response includes configured, configuration_status ("unavailable" when runtime config cannot be resolved; omitted when config loads normally), provider, proxy_api_base (admins only), model, auth_mechanism, transport, masked secret markers (proxy_api_key_masked, static_headers_json_masked, oauth_client_secret_masked, oauth_service_account_header_values_json_masked), the non-secret OAuth fields above, oauth_custom_header_descriptors, oauth_service_account_header_value_keys (sorted field_key entries without exposing secret values), oauth_service_account_header_issue, and allow_user_oauth_fallback_for_non_interactive. Non-admin callers receive a sanitized subset of these fields (including transport, oauth_custom_header_descriptors, and the masked markers, but not proxy_api_base or raw secret values) so per-user OAuth flows can show required custom headers without granting full provider configuration access. The Reconfigure form in Config -> AI Providers keeps the last saved descriptor list when oauth_custom_header_descriptors is omitted from the status payload, falls back to the backing LiteLLM Proxy connection copy when the field is present but normalizes to an empty array, and uses oauth_service_account_header_value_keys to show that a service-account header value is already configured even when the backing connection copy does not expose shared defaults.

LiteLLM Proxy configuration operation response fields

POST /api/config/llm-providers/litellm-proxy and PUT /api/config/llm-providers/litellm-proxy return LlmOperationResponse: For PUT, initialize_models: true explicitly requests initial model-assignment setup. Routine configuration or credential updates leave existing assignments unchanged unless a non-empty model is supplied. The save validates the complete candidate, including any secret_updates, before committing it; a failed candidate save leaves the previously stored proxy credential in place.

LiteLLM Proxy connection test request fields

For an Anthropic-family probe, Genesis removes one trailing /v1 from the configured proxy base URL, then posts to /v1/messages. When request fields are omitted, POST /api/config/litellm-proxy/test-connection fills gaps from saved provider metadata merged with the same runtime LiteLLM Proxy config used for GET display. In service_account OAuth mode, Genesis sends descriptor values with applies_to token_exchange on the OAuth token request, forwards applies_to downstream headers during client-credentials token acquisition, and sends downstream headers on the proxy /models probe. When the effective transport is anthropic, Genesis also probes the configured model (from saved assignments when available, otherwise the first model returned by /models) via /messages. When the effective transport is auto, Genesis resolves the probe from the configured model or the models returned by the proxy, and probes /messages when an Anthropic-family model is among the candidates.

LiteLLM Proxy connection test response

POST /api/config/litellm-proxy/test-connection returns the standard success / message payload. When auth_mechanism is oauth and the effective oauth_credential_policy is per_user_oauth, Genesis returns success: true with a message that service-account testing was skipped and may include details.discovery_deferred: true because runtime calls authenticate with each user’s browser grant instead of client credentials. When transport is anthropic and the probe succeeds, the success message notes that the Anthropic /messages probe succeeded. Submitted secret references are resolved before testing the proxy API key, static headers JSON, OAuth client ID, and OAuth client secret. The OAuth client secret is not read when the effective policy is per_user_oauth. If a submitted reference cannot be resolved, the test fails with details.reason: "credentials". Failed tests may include details.reason to identify the failure category:

Embedding configuration endpoints

These routes back the Embeddings tab on Config -> AI Providers and are independent from the main chat-provider assignments. Persisted active values are stored in APP_CONFIG at embedding.active_provider and embedding.active_model. Per-provider embedding preferences (saved during setup before activation) are stored at providers.<provider>.models.embedding_model. GET /api/config/models/embeddings/config is read-only and accepts either access_config_panel or view_model_config. The PUT route and the other embedding model routes require access_config_panel. After a committed provider-configuration change, Genesis resends the current embedding identity when Twin’s lineage tool is registered; unchanged identities can be refreshed without a document re-index. See Digital Twin reload behavior. POST /api/config/llm-providers/embedding-config/test accepts the embedding configuration request body and returns success, message, optional warning, and dimensions. The live embedding request has a 15-second timeout. The effective embedding identity is resolved in this order:
  1. A complete embedding.active_provider plus embedding.active_model pair from APP_CONFIG is authoritative. EMBEDDING_PROVIDER and EMBEDDING_MODEL are default_env_var seeds for those fields — applied only while no value is stored, and outranked by any stored pair. LITELLM_EMBEDDING_MODEL is no longer read as an input.
  2. When no complete active pair exists, an eligible Cortex mode override may provide the runtime identity.
  3. Otherwise, the identity is unset or incomplete. Genesis does not infer an OpenAI fallback.
Document-index writes fail closed when the identity cannot be used. The API returns HTTP 409 with error_type: "embedding_not_configured" when no identity resolves, or error_type: "provider_not_configured" when the selected provider lacks required configuration or credentials. GET /api/config/models/embeddings/models/{provider} returns data.provider, data.models, and data.with_display_names. When building the list, Genesis reads only persisted embedding configuration for the requested provider — it does not inject the globally inferred OpenAI default when another provider is active, so the active embedding model does not leak into unrelated provider pickers. When with_display_names=true, each model is an object with technical_name and display_name (provider prefixes stripped from technical_name). When with_display_names=false, data.models is a string array of normalized technical identifiers. For live-discovery proxy providers (litellm_proxy), Genesis also returns data.discovery_status ("ok" | "empty" | "error") and data.discovery_error (string or null) using the same semantics as GET /api/config/models/main/models/{provider}. When Model Universe already contains materialized litellm_proxy rows, Genesis serves the picker from those entries instead of calling the proxy on every request; live /models discovery is used only before the first materialization or when the universe list is empty. Recommended-default catalogs are still bypassed for these pickers. discovery_status: "empty" reflects what was advertised or materialized before assignment injection.

Stranded embedding corpora response

GET /api/config/models/embeddings/stranded-corpora returns success and a data array. The array contains one row per reconciled recorded identity plus one row per unreadable collection directory. proposable means Chroma metadata carries a complete identity with no contradictory sidecar record. conflict means two complete records disagree. indeterminate means no complete provider/model pair was recorded; Genesis never invents the missing model. unreadable means no trustworthy identity could be extracted.

Test an embedding model without saving

POST /api/config/llm-providers/embedding-config/test requires access_config_panel. Send provider and model, or provider with use_defaults: true; a model-only request resolves its provider through the existing embedding resolver. The route uses already saved provider credentials. It does not test an unsaved API key, save a model preference, activate a provider, or start reindexing.
The response carries success, message, and dimensions. Non-Cortex providers generate a small embedding through the existing provider transport on each test; the live request may consume provider quota and incur charges. The Cortex branch checks Snowflake availability and obtains catalog dimensions; it does not execute a sample embedding, so its successful response alone is not a full embedding inference check. Provider failures remain visible in message.

Embedding configuration request and response fields

PUT /api/config/models/embeddings/config requires either model or use_defaults: true. If provider is omitted while model is present, Genesis attempts to infer the provider from the model identifier. When activate: false, the response includes details.reindex_required: false. details.activated is normally false, but is true when first-save adoption successfully names the first global embedding identity. With apply_if_active: true, details.applied reports whether Genesis applied the selection to the active identity; if allow_reindex: false would change that identity, the request returns details.confirmation_required: true and details.current_model without saving. Retry with allow_reindex: true after confirmation. When activating a provider later (for example via Set Active for Embeddings), Genesis resolves the embedding model in this order: saved providers.<provider>.models.embedding_model, then the packaged yaml default, then recommended/available models from Model Universe.

First-save embedding adoption

Provider configuration saves that persist an embedding preference, and PUT /api/config/models/embeddings/config with activate: false, may return the optional top-level embedding_adoption field. This is the outcome of a one-time attempt to adopt the saved provider/model when no global embedding identity exists. It is separate from the standing embedding identity state returned by provider-overview APIs.
GET /api/config/models/embeddings/config returns the effective runtime provider and model after coherence correction — for example, a Cortex-only catalog model paired with a non-Cortex provider in a Cortex-capable deployment resolves to provider: cortex. At server startup, Genesis also runs a one-time embedding connection smoke test (5-second timeout for LiteLLM-routed providers) and logs failures without blocking boot.

PUT /api/config/models/embeddings/config response details

This route returns the generic OperationResponse shape with top-level success, message, optional details, and optional embedding_adoption when a save triggers a first-identity adoption attempt.
PUT /api/config/models/embeddings/config writes the embedding identity to APP_CONFIG, and GET reads it back from there; EMBEDDING_PROVIDER / EMBEDDING_MODEL act only as seeds for unset fields. Manage embeddings from Config -> AI Providers -> Embeddings, from this endpoint, or through genesis-config.yaml — the same three surfaces that configure a chat provider. Saved values take precedence over env seeds; GENESIS__* overrides pin effective values while set.

Main-provider model list endpoint

GET /api/config/models/main/models/{provider} backs the Change Models modal on Config -> AI Providers. It returns the model lists used by the per-tier pickers (Premiere, Main, Fast). data always includes provider, type (echo of model_type), models, main_models, fast_models, frontier_models, and with_display_names. For providers other than live-discovery proxies, Genesis builds these lists from Model Universe, recommended defaults, and the provider’s saved assignments (the active assignment is injected when missing so the picker can show the current value). For live-discovery proxy providers (litellm_proxy), Genesis prefers materialized Model Universe rows (source_type: "discovered") when present. Opening Change Models or an embedding model picker also calls POST /api/config/model-universe/refresh?provider=litellm_proxy so models added to the proxy after the connection was saved appear without re-saving that connection; tier assignments are not re-inferred. The refresh is throttled per process by llm.proxy_universe_refresh_interval_seconds (default 300). When the universe is empty, Genesis idempotently runs ensure_litellm_proxy_universe_materialized() on this read path (discovery plus upsert when no litellm_proxy rows exist yet) before building the picker lists. If materialization cannot reach the proxy, Genesis falls back to live /models discovery. Recommended-default catalogs are still bypassed. The same discovered set is used for every tier (main_models, fast_models, and frontier_models). The saved assignment may still be injected into those tier lists for picker continuity even when the proxy did not advertise that model. When no tier assignments are persisted yet, Genesis read-only infers premiere/main/fast technical defaults from the materialized universe for picker preselection; this inference does not persist or activate the provider. When with_display_names=true, each litellm_proxy model object may include underlying_model (string or null) — the backend model id from proxy catalog metadata when known. Proxy-only fields on data:

Model Universe list item fields

GET /api/config/model-universe/models returns data[] items with the standard model metadata plus proxy-specific fields when present: Discovered LiteLLM Proxy rows are reconciled on later saves when discovery is complete; custom rows are never overwritten by materialization.

POST /api/config/model-universe/test-model

Test a model using the provider’s saved credentials. The llm mode runs a minimal completion; embedding sends one embedding request with a 15-second timeout and returns the vector dimensions in details on success. Each call spends one request against the provider and requires access_config_panel. The response contains success (boolean), message (string), and optional details (object). For a successful embedding test, details.dimensions is the embedding vector size.

POST /api/config/model-universe/refresh

Re-discover a live-discovery proxy provider’s models and update Model Universe. The Change Models modal, Embeddings model step, and new-connection models step call this automatically for litellm_proxy. Static-catalog providers return HTTP 400. When /models discovery fails, Genesis returns success: false with message Could not refresh the model list; the saved list is unchanged and leaves existing materialized rows untouched. Response fields: Tier assignments are not re-inferred — only the universe snapshot is refreshed.

Provider seed defaults for main assignments

GET /api/config/models/main/defaults/{provider} returns the technical and display default model map Genesis uses when Change Models resets a provider to shipped defaults or when PUT /api/config/models/main/assignments is called with use_defaults: true. A PUT /api/config/models/main/assignments that sets default_chat also assigns it to supervisor, harvester, and knowledge_server, except a role whose assignment_sources entry is default (every role, under use_defaults: true without sources), which keeps its catalog seed. For databricks and cortex those seeds equal default_chat. The shipped OpenAI main chat default is gpt-6.1-sol; its catalog seeds fast_model, harvester, and knowledge_server on gpt-6-luna. The shipped Azure OpenAI defaults are the gpt-5.5 deployment name for every role except fast_model, which is gpt-5-mini. Startup reconciliation applies the same seeds to roles still managed by defaults, so save and restart agree. For litellm_openai, the shipped default_chat is openai/gpt-6.1-sol and fast_model is openai/gpt-6-luna; both are included in this endpoint’s provider default map. For litellm_anthropic, default_chat and premier_model are anthropic/claude-opus-5-5 and fast_model is anthropic/claude-haiku-4-5; for litellm_bedrock they are global.anthropic.claude-opus-5-5 and global.anthropic.claude-haiku-4-5-20251001-v1:0.

Main-provider assignment fields

The main-provider assignment endpoints expose model tier assignments, source metadata, and a provider-level inherited chat tier:
  • GET /api/config/models/main/assignments
  • GET /api/config/models/main/assignments/{provider}
  • PUT /api/config/models/main/assignments
GET /api/config/models/main/assignments is read-only and accepts either access_config_panel or view_model_config. The provider-specific read and the PUT route require access_config_panel. Each GET assignment response reads the active provider, assignments, source metadata, inherited chat tier, and capabilities from one current APP_CONFIG snapshot. These fields therefore describe the same configuration state instead of mixing values from separate reads or a stale response cache. When PUT /api/config/models/main/assignments targets a provider that does not yet have saved credentials, Genesis can still persist the submitted assignments and default_model_tier without changing the current main_provider. Configure that provider’s credentials and activate it later to make the saved assignments live. A successful assignment save is available to the running process immediately; it does not require a server restart. Saving assignments without activation still leaves the current main_provider unchanged. When provider is litellm_proxy, Genesis calls ensure_litellm_proxy_universe_materialized() before saving assignments so underlying-model metadata is available for the Change Models picker even when the universe was never populated by a prior provider save. On PUT /api/config/models/main/assignments, Genesis validates each non-empty assignment value (after display-name conversion) against LiteLLM’s provider-prefix routing rules before persisting. Bare model names with no / (for example gpt-5 or an Azure deployment name) are accepted — the active provider strategy prefixes them at request time. Slash-prefixed values are accepted only when the first segment is a recognized LiteLLM provider prefix (for example azure/gpt-5 or bedrock/global.anthropic.claude-opus-5). Values whose first segment is not a recognized provider — such as an endpoint hostname genesis-azure-1/gpt-5 — are rejected with HTTP 400 and a message naming the offending task type and model value, for example: Model assignment 'default_chat'='genesis-azure-1/gpt-5' is not routable: 'genesis-azure-1' is not a recognized LiteLLM provider. Use a bare model name or prefix it with a valid provider (e.g. 'azure/<deployment>', 'bedrock/<model>'). Empty or null assignment values are skipped. This check is not applied when provider is litellm_proxy, because proxy model aliases are admin-defined free-form strings that may contain / yet still resolve via the proxy’s own alias table.

Context graph scanner model tier

Twin’s context-graph scanner uses a chat model for bulk description generation. Genesis stores the tier in llm.twin_scanner_model_tier (default fast) and resolves it against the active provider’s tier assignments (fast → fast_model, main → default_chat, frontier → premier_model, falling back to the provider’s main chat model when the chosen tier has no assignment). PUT request body: GET and PUT responses include success, tier, and models — a map of each tier name to the effective model id for the active provider (values may be null when the provider or its assignments cannot be resolved).

SCIM provisioning endpoints

These endpoints implement Phase 1 inbound SCIM provisioning. They are mounted outside the authenticated /api surface at /scim/v2/... and authenticate with Authorization: Bearer <scim_token> using the SCIM bearer token instead of a Genesis PAT. For Databricks App behavior across these routes and the admin endpoints, see SCIM admin configuration endpoints. A machine-readable SCIM OpenAPI artifact is maintained in docs/features/scim-openapi.yaml in the Genesis repository. Genesis intentionally advertises ETag support as false for this release; clients should not rely on If-Match concurrency semantics.

SCIM metadata endpoints

SCIM user and group resource endpoints

Shared SCIM list query fields

SCIM user create and replace request fields

SCIM patch request fields

SCIM group create and replace request fields

User resource responses return id, externalId, userName, displayName, active, emails, roles, name, and meta. Group resource responses return id, displayName, members, and meta. List routes return the standard SCIM ListResponse envelope with schemas, totalResults, startIndex, itemsPerPage, and Resources.

Genesis enhancement report artifact endpoints

These routes are mounted under /api/genesis-enhancements/... and back the Support and Enhancements report workflow.

Report list endpoint

GET /api/genesis-enhancements/reports

This endpoint returns the standard response envelope with success plus reports, where each item uses the same base report shape returned by GET /api/genesis-enhancements/reports/{report_id}. Reports created through this API record the requesting user’s ID, email, and display name as the creator. These values come from the request’s user context, not creator fields supplied in the request body. When submitting a report with no recorded creator, Genesis records the submitting user’s identity.

Discard a report

POST /api/genesis-enhancements/reports/{report_id}/discard

This route accepts either access_config_panel or manage_genesis_enhancements. For the storage cost, see discard_enhancement_report. A successful response has success: true and a report object whose status is discarded. Its discard object records the following fields:

Artifact-backed report creation fields

POST /api/genesis-enhancements/reports accepts these artifact-related body fields in addition to the standard report fields: When a report has attached artifacts, GET /api/genesis-enhancements/reports/{report_id} returns an attachments array. Each attachment entry includes these fields:

Report detail bundle preview fields

GET /api/genesis-enhancements/reports/{report_id} also accepts these fields for the report-detail drawer: When bundle preview generation succeeds, report.bundle can include:

Version-aware ticket fields

Report responses can also include these version-tracking fields under report.ticket: Bundle preview enrichment is best-effort. If preview generation fails, Genesis still returns the base report payload instead of failing the entire request.

Artifact download endpoint

GET /api/genesis-enhancements/reports/{report_id}/artifacts/{artifact_id}

Full report bundle endpoint

GET /api/genesis-enhancements/reports/{report_id}/download-bundle

This ZIP contains report.md, report.json, scrub_manifest.json, and all attached artifact files for the report. When enhancement bundle scrubbing is enabled, Genesis redacts supported text artifacts before packaging and records the outcome in scrub_manifest.json; binary attachments pass through unchanged. If the report includes a nested thread_export.zip, that inner ZIP carries its own scrub_manifest.json; support-bundle workflows rewrite role: "tool" output content in thread.json and message_log.csv to schema-only descriptors by default and record the applied mode plus redaction counts there. The same single ZIP is also the only attachment Genesis sends during online report submission. If Genesis cannot build a safe bundle or the receiver cannot accept multipart uploads, POST /api/genesis-enhancements/reports/{report_id}/submit returns success: false instead of falling back to a bundleless submission.

Support email draft endpoint

GET /api/genesis-enhancements/reports/{report_id}/email.eml

This endpoint returns message/rfc822 content with X-Unsent: 1 so Outlook and Thunderbird open the file as a draft the user is composing. Apple Mail and browser-based webmail flows should use the mailto: fallback together with the plain ZIP download instead.

Enhancement settings endpoints

PUT /api/genesis-enhancements/settings is a partial update. Omit a field to keep its current stored value. When autosend_enabled is false, report submission paths force offline delivery regardless of mode_preference. On non-SQLite Snowflake deployments, enabling autosend upserts the receiver host parsed from receiver_base_url into CUSTOM_ENDPOINTS under egress group GENESIS_ENHANCEMENTS with type CUSTOM; disabling autosend removes that endpoint; changing receiver_base_url while autosend stays enabled replaces the previous host. Endpoint reconciliation failures are logged but do not fail the settings update. Each successful PUT also invalidates the cached EAI assignment check for report_eai_reference.

Connectivity and receiver settings fields

Bundle-delivery settings fields

Dormant thread review endpoints

These routes are mounted under /api/genesis-enhancements/dormant-review/... and back the Genesis Self-Improvement panel on Config -> Support and Enhancements plus the compact dormant-review summary shown in Pulse. The UI label uses Genesis Self-Improvement; the API path and stored metadata still use dormant-review. Status, saved settings, and manual run-once use access_config_panel. Scheduler start and stop require manage_scheduler. PUT /api/genesis-enhancements/dormant-review/settings is a partial update. Omit a field to keep its current stored value. The current build does not accept autosubmit_enabled in the update body and always normalizes that response field back to false. POST /api/genesis-enhancements/dormant-review/scheduler/start and POST /api/genesis-enhancements/dormant-review/scheduler/stop do not accept a request body. On success they return success: true with data.job_id: "dormant_thread_review".

Dormant-review settings fields

Run-once request fields

When POST /api/genesis-enhancements/dormant-review/run-once omits a field, Genesis uses the currently saved setting for that value.

Dormant-review status response fields

queue, recent_windows, and recent_failures reuse the review-window item fields below.

Scheduler state fields

Review-window item fields

Generated-report item fields

Latest run summary fields

Cursor Agent admin endpoints

These routes back report-specific Cursor Agent actions. All of them require access_config_panel. The Software Factory page reads the provider-neutral factory endpoints below instead. The web client exposes the Software Factory route only when GET /api/config/frontend returns software_factory_enabled: true.

Software Factory operator endpoints

These back the Software Factory page and read the coding lane rather than a single enhancement report. GET accepts either access_config_panel or manage_genesis_enhancements; stopping a run requires manage_genesis_enhancements alone, because ending someone else’s run is an operator action rather than a read.

Software Factory response fields

The status and lane responses wrap their payload in data. The lane endpoint’s data has the same fields as data.lane in the status response. Current runs include every non-terminal run, newest first; recent_runs is the paginated history window, also newest first.

Software Factory lane fields

Software Factory run fields

Each run in current_runs or recent_runs has this projected shape. verification and pr_status contain backend-provided evidence when available; review is the claim’s review-loop projection when one exists.

Software Factory review fields

Software Factory stop response fields

Cursor Agent response envelope fields

Global status summary fields

Coding Agent run metadata fields

These fields can appear on the report-scoped cursor_agent object, on the global cursor_agent.active_run / cursor_agent.latest_run objects, and on entries inside cursor_agent.recent_runs unless noted otherwise. The Factory-only factory_result field is top-level in AutoDev action responses; it is not part of the compact Cursor Agent history entries. Entries inside recent_runs omit prompt_preview and output_lines to keep the global status payload compact.

Factory review result fields

factory_result is returned by AutoDev review actions and by status responses for enrolled Factory runs. Follow current_agent_id and current_run_id until terminal is true, including after the current coding child reaches a terminal status. The server owns the final Linear settlement. The repository skill and orchestration context used by Factory workers is runtime-provided and is not represented as an additional response field or configuration key; see autodev_lane_tools.

Factory runtime recovery fields

An enrolled Factory run exposes factory_result.recovery when a coding child stops at a trusted runtime boundary or when a recovery answer is awaiting adjudication. The recovery controller preserves the task’s orchestration thread and branch, but starts a fresh coding run after a retry, supported context or environment remedy, or accepted human answer. It never resumes the previous SDK session. Use recovery.snapshot_id as the optimistic-concurrency token for autodev_run_action(action="recover").

Coding Agent verification fields

Provider backends that produce verification evidence include a verification object on report-scoped cursor_agent metadata, on global active_run / latest_run, on tool GET_CURSOR_AGENT_STATUS responses, and on entries inside recent_runs. For Claude Code runs, verification uses the effective target_branch base ref configured by genesis_enhancements.coding_agent.claude_code.target_base_ref (default main), rather than always comparing against main. Genesis fetches Cursor Cloud artifacts only after the run reaches a terminal state (completed, failed, cancelled, or disabled). While a run is still active, verification.verdict is unverified with reason verification artifacts are fetched only after the run is terminal. After FOLLOWUP_CURSOR_AGENT advances cloud_run_id, Genesis resets verification to unverified until the new run finishes and fresh artifacts are available. Stored verification is bound to the current cloud_run_id through verified_run_id; Genesis never surfaces a verdict recorded for a different run. Genesis lists and downloads artifacts/verification.json and, when the envelope declares a python_unit_tests check, optionally artifacts/junit.xml from the Cursor Cloud agent artifact store. Each artifact download is capped at 8 MiB.

Cursor Agent verification check item fields

Cursor Agent cloud pull request status fields

Tool GET_CURSOR_AGENT_STATUS responses that poll a specific Cursor Cloud run by agent_id include a top-level pr_status object when the run has a pr_url. Genesis fetches check state with gh pr checks and does not cache this snapshot across keyed polls.

Cursor Agent git overview fields

The git object is attached when Genesis can safely inspect the saved working_directory. The comparison base ref defaults to origin/main and can be overridden with CURSOR_AGENT_GIT_BASE_REF.

Cursor Agent pull request status fields

Genesis caches the nested pr_status snapshot for 30 seconds per worktree / pull request. updated_at records when that cached snapshot was last rebuilt.

Cursor Agent pull request check summary fields

Cursor Agent pull request check item fields

Cursor Agent CodeRabbit summary fields

When a report has no saved Cursor Agent run yet, GET /api/genesis-enhancements/reports/{report_id}/cursor-agent/status returns a lightweight cursor_agent object with enabled, configured, and status. The status is idle when the launcher is ready, or disabled plus disabled_reason when the integration is not configured. Agents can poll a specific Cursor Cloud run by agent_id (and optional cloud_run_id) through genesis_enhancement_action with GET_CURSOR_AGENT_STATUS. The returned cursor_agent object includes the same verification rollup described above once the run is terminal, plus pr_status with live GitHub CI checks when the run has a pr_url. Treat a cloud run as fully evidenced only when verification.verdict is passed and pr_status.checks.green_is_earned is true; unverified is not a pass, and all_passed alone does not prove behavioral lanes ran. That keyed lookup is not exposed as a separate REST route; the REST admin endpoints remain report-scoped or global-summary only. Agents can steer an existing Cursor Cloud agent with genesis_enhancement_action and FOLLOWUP_CURSOR_AGENT. This action is also tool-only (no REST route). It requires agent_id and followup_prompt, works only on the cloud adapter, and posts a new run to POST /v1/agents/{agent_id}/runs so the follow-up inherits the agent’s repo, branch, and model. Cursor Cloud accepts the follow-up only when the agent is idle; while a run is still active the tool returns success: false, busy: true, and retryable: true. Poll GET_CURSOR_AGENT_STATUS until the run reaches a terminal state, then retry. On success the response includes cursor_agent with agent_id, cloud_run_id, and status for the new follow-up run; Genesis advances its tracked cloud_run_id, resets started_at, and clears verification to unverified so subsequent status polling follows the follow-up run and waits for new verification artifacts. For tool-started cloud runs, genesis_enhancement_action with START_CURSOR_AGENT accepts an optional model parameter that overrides genesis_enhancements.cursor_agent.model for that run when task_prompt is provided. Genesis maps the chosen slug to model: { "id": "<slug>" } on POST /v1/agents the same way as the REST start route. Report-scoped tool starts that pass only report_id use the deployment config the same way as the REST start route above.

Advanced settings read and update endpoints

Configuration paths use lowercase dot-notation (for example server.debug). All advanced-settings routes normalize path arguments to lowercase before schema lookup, validation, and secret-field guards — mixed-case paths such as Server.Debug resolve to the same field as server.debug. GET /api/config/advanced/schema, GET /api/config/advanced, and GET /api/config/advanced/{section_name} return field metadata plus current values. Each field includes is_default: true when no value has been explicitly stored in APP_CONFIG for that path (startup NULL scaffold rows do not count as an explicit operator choice). When is_default is true, the resolved value comes from the config priority chain (default_env_var or schema default). Current-value responses from GET /api/config/advanced and GET /api/config/advanced/{section_name} include forced_reason on each field, normally null. On Genesis Desktop, desktop.local_cli_enabled has forced_reason: "desktop" and its effective value is true regardless of the stored value. Attempts to update or reset a setting Desktop holds fixed are refused without changing the stored value. The section objects returned by GET /api/config/advanced and GET /api/config/advanced/{section_name} also include warnings, an array of operator warnings about effective configuration; it is empty when there are no warnings. The CLI Terminal warning condition and remediation are documented in Advanced Settings. The schema description for genesis_enhancements.coding_agent.sandbox_claude.session_retention_seconds documents when an unrecovered or unpublished coding run’s workspace is released; see Provider-neutral coding-agent backend. The collection read used by the Advanced Settings page batches its storage and secret-status reads into two database queries; see Advanced Settings for the resulting Snowflake-hosted page-load behavior. Non-admin callers with access_config_panel still receive most advanced settings on the read routes, but Genesis omits admin-only paths from GET /api/config/advanced/schema, GET /api/config/advanced, and GET /api/config/advanced/{section_name} responses. PUT /api/config/advanced/value, PUT /api/config/advanced/values, and POST /api/config/advanced/reset return HTTP 403 when a non-admin includes any admin-only path. Admin-only paths: sandbox_runner.* paths control container execution, networking, and which bots run arbitrary code in a sandbox — all are admin-only. See Advanced Settings — Sandbox thread runner for field semantics. Sandbox thread-runner tokens are not general API credentials. On the MCP transport, send the live token with Authorization: Bearer gsk_...; the optional x-genesis-thread-id header, when supplied, must identify the same thread. Genesis accepts the request only when the token belongs to that thread, resolves a non-system owner, and the route and HTTP method are in the sandbox capability contract. The request then runs as the owning user, so normal RBAC and tenancy checks still apply; invalid or expired tokens return 401, and routes outside the contract return 403. The same contract applies to the REST calls MCP makes on the agent’s behalf. See Sandbox thread token controls. wiki.correction_classifier_model selects the non-Claude model used by the H2 correction scan; wiki.correction_learning_write_enabled and wiki.correction_write_min_confidence control whether the classifier persists auto-supersession learnings and the minimum classifier confidence required to write. These settings are checked live when the scan or verdict runs (no restart). See Advanced Settings → Team Wiki / Librarian settings for field semantics. Secret fields (is_secret: true) and paths locked by GENESIS__* overrides are refused on both write routes (same guards as reset).

PUT /api/config/advanced/value request fields

PUT /api/config/advanced/values request fields

PUT /api/config/advanced/value and PUT /api/config/advanced/values response fields

Advanced settings reset endpoint

POST /api/config/advanced/reset deletes stored configuration values so each path returns to “never explicitly set” and resolves through the normal config priority chain — a field’s default_env_var when that environment variable is set, the schema default otherwise. Genesis does not write the schema default back into the database. For an env-backed leaf that Genesis previously bridged, the reset also restores the deployment’s original value from before that bridge overwrote the environment variable once no other path is still bridging it; if it was unset, Genesis unsets it again. This makes the next resolution use the deployment default rather than the value that was just reset. The whole request is refused (nothing deleted) when any path is not a schema field, is a secret field, or is locked by a GENESIS__* runtime override (the same guards as PUT /api/config/advanced/value). Secret fields must be reset through /api/config/secret-fields/... instead.

POST /api/config/advanced/reset request fields

POST /api/config/advanced/reset response fields

Manual thread-archive run endpoint

POST /api/config/advanced/archive/threads/run runs thread archival immediately using the currently saved archive settings. This route does not accept a request body. The endpoint returns 400 when threads are stored in Postgres; see thread archive settings for the storage-engine limitation. Each archived object is a ZIP bundle built from the thread’s stored source files and uploads, with message_log.csv included when available. If a prior run already uploaded the bundle, Genesis verifies that remote object and finishes local cleanup instead of rebuilding it.

Thread-archive run response fields

Thread-archive run summary fields

Bot directory cleanup endpoint

POST /api/bot-directories/cleanup cleans up bot-thread directories under {STORAGE_PATH}/threads that do not match the bot registry. Genesis quarantines invalid directories to the reversible graveyard at {STORAGE_PATH}/graveyard, renames wrong-case directories to the canonical bot_id, and merges disjoint thread sets into the canonical bot. It never hard-deletes thread data. This route does not accept a request body.

POST /api/bot-directories/cleanup

Returns BotDirectoryCleanupResponse with success: true and a data object describing the run. Each actions[] item:

AWS connection bucket listing endpoint

GET /api/connections/aws/{name}/buckets lists the S3 bucket names visible to the resolved AWS connection. Genesis uses this helper route to suggest values for archive.threads.bucket in Advanced Settings.

GET /api/connections/aws/{name}/buckets

AWS connection bucket listing response fields

Connection registry and PAT-capability behavior

These routes live under /api/connections/... and back the Connections UI. Genesis treats auth types pat, api_token, and api_key as PAT-class saved credentials. When auth_type is omitted, PAT-style fields such as access_token are still treated as PAT-class for policy enforcement.

Connection AI provider endpoints

These laptop-mode routes list AI available through saved connections and let a person choose whether Genesis uses it. GET /api/connections/ai-providers returns one entry per connection that brings AI the caller can switch to, including Snowflake Cortex. Entries include provider, label, source_connection_id (<type>.<name>), chat_models, embedding_model, configured, active, reason, and sign_in_required. A Cortex entry that requires Snowflake OAuth reports sign_in_required: true; choosing it opens the browser sign-in before configuration is applied. Activating replaces the current chat provider. Choosing chat_and_embeddings also switches embeddings and can re-index existing content; chat_only leaves the embedding provider unchanged. not_now declines the offer. The response’s data includes success, message, and optional warning_message. On Genesis Desktop, these connection-supplied AI alternatives are not available: the list and per-connection offer omit them, and attempts to submit chat_only or chat_and_embeddings are refused with HTTP 403. The connection itself remains usable for its other supported features. Snowflake also exposes restricted_callers_rights in the connection registry when Genesis runs in SPCS. That auth type is credential-free (credential_free: true, empty fields) and is not PAT-class. Saving it sets privatelink_same_account_rcr: true on the locator. POST /api/connections/test and POST /api/connections/{connection_type}/{name}/test return an advisory when RCR is validated outside SPCS because the SPCS caller token is required. BigQuery exposes application_default in its auth_types map for Application Default Credentials (Runtime Identity). The registry removes this auth type for callers without manage_all_connections, and the create, update, and test routes reject it for those callers. An update is also rejected when it transitions an existing connection into or out of this mode. See Connections — BigQuery for the stored fields and runtime credential behavior. For databricks_lakebase, the registry exposes app_identity only in a Databricks App and only to callers with manage_all_connections. Create, update, and test operations reject non-admin callers, including updates that transition a connection into or out of this shared runtime identity. See Connections — Databricks Lakebase connections for the target endpoint and credential rules. For Azure DevOps, the pat entry in auth_types includes principal_fields: ["pat"]. This declares the PAT field that users may bind through the personal credential endpoints when a shared Azure DevOps connection enables personal_credentials. An auth type may include an availability object with status and message. The Connections UI keeps that auth type visible but disabled and displays the message. The API is authoritative: POST /api/connections returns 400 when a request names an unavailable auth type, while POST /api/connections/test returns success: false with the same message. Any non-empty availability marker means the mode is not selectable; an auth type without the marker remains available. The palantir_foundry registry entry uses this marker for auth_type: "oauth2" with status: "coming_soon" and message Coming soon — contact Genesis to enable. In a Databricks App, the same marker disables BigQuery application_default, AWS and MWAA irsa, MWAA snowflake_workload_identity, and Snowflake restricted_callers_rights, with a mode-specific message. The Databricks App also refuses AWS or MWAA assume_role when access_key_id is missing, since that would use the App host’s credentials. These refusals apply to create, update, test, and saved-connection use; UPDATE also checks the effective stored auth type when the locator omits auth_type. See Connection mode chooser for the alternatives offered for these modes. Palantir Foundry’s selectable personal_token mode stores a non-secret base_url and a secret token. See Palantir Foundry connections for the UI fields and Palantir Foundry for the connector tools. Agent discovery follows the same policy: connections_action LIST_TYPES omits unavailable auth types, and HELP does not propose them. This includes the Databricks App restrictions above and registry modes marked unavailable such as coming_soon. When Genesis runs as a Databricks App, /api/connections/registry omits the Databricks pat auth type for every caller and offers databricks_app_user (Signed-in Databricks user) for App-forwarded user-token SQL. Outside a Databricks App, the registry omits databricks_app_user because that runtime cannot provide the forwarded token. Databricks PAT connections are refused on create, locator updates, saved-connection tests, and auth-mode-only updates that would make a stored PAT usable inside the App; previously saved legacy Databricks rows that contain raw tokens cannot be used. See Connect to Databricks — Choose an authentication mode for supported alternatives and the signed-in-user SQL boundary. | POST | /api/connections/export-bundle | Export selected saved connections (with secrets) as an encrypted .genesis-bundle file. | Requires manage_all_connections. | | POST | /api/connections/import-bundle/preview | Decrypt a bundle and return a dry-run preview without applying changes. | Requires manage_all_connections. | | POST | /api/connections/import-bundle/apply | Import connections from an encrypted bundle. | Requires manage_all_connections. |

Connection test response fields

POST /api/connections/test and POST /api/connections/{connection_type}/{name}/test return a ConnectionTestResponse with the test result in data: An unsaved-credentials test does not update last_seen_working_at. The UI can offer the remedy as an explicit retry for unsaved form values; callers must apply it only with the user’s choice. A saved-connection test returns the remedy for the caller to display, but does not change the saved connection. The status code is diagnostic metadata from the probe, not the HTTP status of the Genesis API response. For example, a failed credential check can return data.success: false with data.status_code: 401, while a transport failure has no upstream response and returns data.status_code: null. POST /api/connections and PUT /api/connections/{connection_type}/{name} accept additional_hosts as a list of up to four provider-allowed bare host authorities. The field is returned on connection list and detail rows. For type: "mcp", the locator can also include these fields:

POST /api/connections/mcp/snippet/parse

Read a pasted Claude Code .mcp.json server entry without saving it or contacting the named server. The request can contain a complete mcpServers object, one named entry, or a bare server object. The parser accepts at most one server and limits text to 64,000 characters. The response is McpSnippetParseResponse: Refusals are returned as ordinary 200 responses so the Connections UI can explain them. Examples include invalid JSON, multiple servers, unsupported stdio transport, environment-variable references, and unusable headers. The registry includes the outbound MCP Server connector (type: mcp in connection requests, category tools). Its supported auth_types are none, bearer_token, oauth_client_credentials, and user_oauth. user_oauth accepts optional inline OAuth endpoints because Genesis can fill them from MCP metadata discovery and dynamic client registration. Creating or updating an MCP connection discovers and registers its remote tools; deleting it removes those tools. Interactive OAuth initiation is limited to the connection creator or an administrator because the resulting grant is shared by the connection’s agents. When the PAT gate blocks one of these routes, Genesis returns 403 with: Your role does not allow PAT-based tool connections. Use OAuth instead, or ask an admin to enable PAT capability for your role.

Outpost API

These endpoints administer customer-run Outposts and back the connection form. Every route requires authentication. Administration routes require access_config_panel; the route-lookup and choices endpoints require authentication only. In the /api/outposts response, each Outpost not yet issued its own key has a tunnel_key_warning (string or null): it cannot sign in until the key is issued, and the warning says how. It is null once the Outpost has its key. The response’s data.unavailable_reason is a string when the Snowflake Native App has no Outpost tunnel endpoint and null where Outposts can be used. The status response includes reachable, diagnosis (state, cause, message, and fix), routes, and tunnel_key_warning when applicable. Each route reports its host, port, using connections, and any refusal. A route withheld because multiple Outposts claim the same host name has cause host_conflict; Genesis leaves that host unrouted. unproven means Genesis received a control answer without a valid proof from that Outpost’s key and did not use the answer. When the Outpost’s /health reports a release version, status includes it as version; earlier releases and status responses without a health result return null. The route-lookup responses return data.outpost as the selected Outpost name or null. To route an individual connection, include its Outpost name in the connection locator’s optional outpost field. Rows returned by GET /api/connections also include outpost_route when a live route applies: outpost, routed_by (chosen or pattern), and the matching pattern when routing came from a host pattern. Other connection response routes return this field as null.

Connection visibility rules

Genesis applies these rules before returning connection list or detail payloads (routes that reference “standard connection-visibility rules”): If Genesis cannot load access grants for a shared connection, it treats the connection as not visible (fail closed). Credential resolution for personal connections is also owner-only; other users receive a permission error instead of resolved secrets.

GET /api/connections

GET /api/connections returns FilteredConnectionListResponse. The data[] array contains only rows matching the active search, category, owner, scope, and auth filters. Filtering and sorting are applied after visibility rules, so callers never receive rows they are not allowed to see. The response also includes total_visible, the number of visible rows before active filters, matched_count, the number of rows in data[], and facets. facets.owners[] and facets.auth_types[] contain value, label, and count for the full visible set before active filters. Use these facets to populate stable filter choices and counts without making a separate request. Connection list and detail responses do not return locator credentials. Fields whose names identify secrets are represented by redacted placeholders; credential values embedded in URLs, connection strings, DSNs, or other locator text are masked while the remaining text is preserved. Secret references remain visible as names. This applies even when the caller can view other sensitive connection metadata; it does not change the stored locator used to connect. Legacy database connections marked hidden=true in CUST_DB_CONNECTIONS are omitted from data[] unless include_hidden=true and the caller has manage_all_connections. The virtual System Snowflake connection uses the separate AppConfig flag connections.hide_system_snowflake; when that flag is true the row is omitted unless include_hidden=true for an admin caller. Connection rows in list responses can also include scoped-default metadata:

GET /api/connections/system-snowflake/hidden

Returns SystemSnowflakeHiddenResponse with data.hidden (boolean) reporting the current hide state for the virtual System Snowflake SPCS-token connection.

POST /api/connections/system-snowflake/hidden

Request body (SystemSnowflakeHiddenRequest): Returns SystemSnowflakeHiddenResponse with data.hidden set to the persisted state after the write. Genesis stores the flag at connections.hide_system_snowflake in AppConfig (not in CUST_DB_CONNECTIONS).

PUT /api/connections/{connection_type}/{name}

Genesis accepts partial update bodies. These scope-related fields apply on update: An update that includes locator performs a live connectivity check before the request is committed. A failed check returns a validation error and leaves the saved connection unchanged; an update that changes only metadata does not need a live connection attempt. The agent-facing update action documents how conflict resolution selects the credential used for its check.

Connection activity endpoint

GET /api/connections/{connection_type}/{name}/activity

Lists recorded statements run on the selected connection and attributes each statement to the user and credential mode that ran it. This endpoint requires manage_all_connections. The response is ConnectionActivityResponse: success plus data, an array of ConnectionActivityItem objects: The connection match checks both the bare name and the qualified <connection_type>.<name> form, case-insensitively. Connections of different types that share a name can therefore appear together; inspect each item’s connection_id. The call performs a filtered activity-table scan and an anchor lookup, so its scan cost grows with recorded activity.

Personal credential endpoints

Personal credentials are per-user principal bindings for shared connections. They are controlled by the connection’s personal_credentials policy: disabled (default), optional, or required. The authentication mode must declare the principal fields that users may provide. These endpoints never return secret values; responses contain scrubbed binding metadata such as user_id, status, principal_label, updated_at, and is_current_user.

GET /api/connections/{connection_type}/{name}/credentials

Returns ConnectionPersonalCredentialsResponse with data as the visible binding rows. A regular user receives their own binding; a caller with manage_all_connections can review bindings for the shared connection.

PUT /api/connections/{connection_type}/{name}/credentials/me

Request body: The route binds or replaces the authenticated user’s principal and returns ConnectionPersonalCredentialResponse with scrubbed binding metadata.

DELETE /api/connections/{connection_type}/{name}/credentials/me

Revokes the authenticated user’s binding and returns GenericResponse.

DELETE /api/connections/{connection_type}/{name}/credentials/{user_id}

Revokes the specified user’s binding and returns GenericResponse. The user may target only their own binding unless they have manage_all_connections.

Scoped default response fields

The scoped-default routes return ConnectionDefaultsResponse with data:

GET /api/connections/{connection_type}/defaults

This route returns ConnectionDefaultsResponse.

PUT /api/connections/{connection_type}/{name}/defaults/user

This route returns the updated ConnectionDefaultsResponse.

DELETE /api/connections/{connection_type}/defaults/user

This route returns the updated ConnectionDefaultsResponse.

PUT /api/connections/{connection_type}/{name}/defaults/roles/{role_name}

Requires manage_all_connections. The target connection must be shared, non-legacy, and already accessible to role_name.

DELETE /api/connections/{connection_type}/defaults/roles/{role_name}

Requires manage_all_connections.

POST /api/admin/demo-data/purge

This route accepts no query or body fields. POST /api/admin/demo-data/purge hides every bundled demo legacy database connection and asks Twin to evict that connection’s demo graph and search entries. The bundled set is the union of Twin-managed demo databases and locally seeded SQLite demos, including baseball_sqlite, formula_1_sqlite, hcls_demo_1_sources, credit_card_sqlite, asset_mgmt_sqlite, and lab_source_data. Genesis returns 403 when the caller lacks manage_all_connections.

Connection bundle export / import

These routes support cross-instance migration of saved connections with their secrets. Bundles use AES-256-GCM encryption; the passphrase must be at least 16 characters. Bundles expire 24 hours after export. Uploads larger than 10 MiB return 413. The Connections guide covers the import workflow and auth-mode inference for bundle entries that omit auth_mode.

POST /api/connections/export-bundle

Returns 200 with Content-Type: application/octet-stream and Content-Disposition: attachment; filename="connections.genesis-bundle". Returns 400 when the passphrase is too short, a requested connection is not found, one or more selected connections fail to export (message lists failed type.name ids), or there are no connections to export. Returns 403 when the caller lacks manage_all_connections.

POST /api/connections/import-bundle/preview

Multipart form upload. Returns ConnectionBundlePreviewResponse with: Returns 400 on decryption failure, expired bundle, or invalid format. Returns 413 when the uploaded file exceeds 10 MiB. Returns 403 when the caller lacks manage_all_connections.

POST /api/connections/import-bundle/apply

Multipart form upload. Returns ConnectionBundleApplyResponse with: Returns 400 on decryption failure, expired bundle, invalid conflict_resolutions JSON (including keys or values outside "skip" / "overwrite"), or per-connection apply errors surfaced as ValueError. Returns 413 when the uploaded file exceeds 10 MiB. Returns 403 when the caller lacks manage_all_connections.

Health endpoints

GET /api/health

Readiness-oriented health check. Returns 200 only after startup has reached 100% progress; returns 503 with startup progress while the server is still initializing. For pure liveness without readiness gating, use GET /api/health/live. When the server is ready but impaired (for example agent warmup did not finish, or embedded Tailscale egress is down), Genesis still returns HTTP 200 so load balancers and SPCS keep routing traffic. In that case status is "degraded" and degraded_reason names the impairment for operators.

GET /api/health/ready

Kubernetes-style readiness probe. Returns 200 with ready: true when the static assets check and error-rate check pass; otherwise returns 503 with the same values in the response detail. The error-rate check passes when the current rate is below 50 errors per minute. Genesis measures that rate over at least one full minute after startup, so during the first 60 seconds it uses a 60-second denominator instead of treating the short uptime as a full-minute rate.

GET /api/health/startup

Startup progress endpoint used by the web UI. Always returns 200 with the current initialization state. When startup reaches 100%, the response includes degraded and degraded_reason (same semantics as GET /api/health) so clients can surface a ready-but-impaired state.

GET /api/version

Returns the live runtime version, the Genesis release carried by the build, and changelog and post-upgrade-advisor payloads used by the web client’s upgrade notice modal and header About Genesis menu. Release notes, changelog details, and the advisor key on the Genesis release version. On Genesis Desktop, version is the desktop app version, while genesis_version identifies the Genesis release. Genesis sets Cache-Control: no-cache on this route so the UI can poll while Eve generates a personalized upgrade summary.

POST /api/upgrade-summary/regenerate

Clears the cached summary for the current runtime version and queues a fresh Eve post-upgrade-advisor run. Requires access_config_panel.

POST /api/upgrade-summary/refresh-inventory

Force-rebuilds the cached install inventory snapshot used by the advisor (bots, Apps, flows, blueprints, connections). Requires access_config_panel.

Runtime health and observability endpoints

These routes are mounted under /api/health/... and back low-overhead runtime diagnostics used by Pulse and other operator tooling.

GET /api/health/detailed

This route returns the broader detailed runtime snapshot used by operator tooling. Its nested thread_service block is intentionally non-blocking: Genesis reads in-memory metrics and worker process liveness without issuing a live worker RPC. The top-level snapshot also includes degraded and degraded_reason with the same semantics as GET /api/health. When no worse condition is already reported, a ready-but-degraded startup sets top-level status to "degraded". The errors.error_rate_per_minute value is calculated over at least one full minute after startup. During the first 60 seconds, Genesis uses a 60-second denominator so a short burst of startup errors does not appear as an inflated full-minute rate. The detailed health status uses this rate for its degraded and unhealthy thresholds, and the readiness probe uses it for its 50-errors- per-minute check. In auth-enabled deployments, callers need access_config_panel. Anonymous callers receive 401; callers without config-panel access receive 403 with detail Config panel access required. In no-auth dev mode the route is open.

child_processes field in /api/health/detailed

The detailed health response includes an in-memory snapshot of child-process supervision. supervised is true only when the supervision job is configured to run in a Databricks App: jobs.child_process_supervisor.enabled is enabled and jobs.child_process_supervisor.interval_seconds is positive. The children object is keyed by child name and reports state for children seen by a supervision pass. The snapshot is process-local and performs no child-process RPC. Configure the job and restart budget in Advanced Settings — Child process supervisor.
Genesis no longer returns a filtered thread_service block for callers without config-panel access. Hard denial supersedes the legacy sensitive-field filtering path.

thread_service fields inside /api/health/detailed

Selected nested thread_service metrics

rpc_metrics.totals fields

rpc_metrics.rolling fields

rpc_metrics.actions fields

Each key is an RPC action name (for example run_compression). Values are per-action counter buckets. Pulse uses these per-action slow counters to attribute long run_compression work separately from read, write, and search RPC latency when deriving the Server tile severity.

boot_metrics.state fields

search_metrics.totals fields

loaded_threads fields

GET /api/health/threads

This route does not accept query parameters.

Thread diagnostics response fields

top_families[] item fields

threads[] item fields

GET /api/health/thread-service/ready

Tokenless operational readiness probe for the thread-service worker. Genesis reads the same non-blocking in-process snapshot used by the thread_service block in GET /api/health/detailed and returns only enabled and active — no live worker RPC. Use this from CI, ingress startup checks, or other automation that must wait for the external worker to come up without config-panel credentials. For full thread-service diagnostics, use GET /api/health/detailed (requires access_config_panel when auth is enabled). For a live health() round-trip, use GET /api/health/thread-service. In auth-enabled deployments, this route is tokenless (same treatment as GET /api/health, GET /api/health/live, and GET /api/health/ready).

GET /api/health/thread-service

This route performs a live health() round-trip against the thread-service worker. Use it for deploy verification or on-demand stuck-worker debugging when you want a real IPC probe rather than the cheaper snapshot included in /api/health/detailed or GET /api/health/thread-service/ready. The timeout is fixed at 10.0 seconds and is returned as timeout_s.
In auth-enabled deployments, callers need config-panel access for this route. Without it, Genesis returns 403 Forbidden instead of a filtered response.

GET /api/health/server-panel-telemetry

This route returns the Pulse Server runtime snapshot. It is cache-backed by default and omits the heavier health block used by broader telemetry flows. When server telemetry is disabled, the response returns enabled=false, schema_version=1, and reason="disabled_by_env".
In auth-enabled deployments, callers need access_config_panel for this route. Anonymous callers receive 401; callers without config-panel access receive 403 with detail Config panel access required. In no-auth dev mode the route is open.

Server-panel telemetry top-level fields

runtime.threads fields

runtime.async_dispatch fields

The Pulse Server snapshot reports the event-loop-native prompt dispatcher under runtime.async_dispatch. It is the prompt dispatch path used when a bot has a registered WebSocket adapter; requests without one use the synchronous fallback described in Chat endpoints.

GET /api/health/slots

Return abridged agent slot usage for authenticated callers. This route intentionally omits user identity fields. For the full payload used by the Pulse CWA Monitor / Slots view, call GET /api/admin/slots instead. In auth-enabled deployments, anonymous callers receive 401. Tokenless load-balancer probes should use GET /api/health or GET /api/health/ready instead. Returns 503 when AgentSlotService is not initialized.

Slots response top-level fields (/api/health/slots)

acquired_slots[] fields (/api/health/slots)

This route does not return user_id, user_display_name, or thread_purpose.

waiting_threads[] fields (/api/health/slots)

This route does not return user_id, user_display_name, or thread_name.

GET /api/admin/slots

Return full agent slot usage, including user identity fields, for operator tooling such as the Pulse CWA Monitor / Slots page. The operational shape matches GET /api/health/slots, but acquired and waiting entries also include identity metadata. In auth-enabled deployments, callers need access_config_panel. Without it, Genesis returns 403 Forbidden with detail Config panel access required. In no-auth dev mode the route is open. Returns 503 when AgentSlotService is not initialized.

Slots response top-level fields (/api/admin/slots)

acquired_slots[] fields (/api/admin/slots)

Includes every field from the abridged /api/health/slots response plus:

waiting_threads[] fields (/api/admin/slots)

Includes every field from the abridged /api/health/slots response plus:

Memory trace logging

Genesis emits periodic memory-trace log lines when the server starts with GENESIS_MEM_TRACE set to a truthy value (1, true, yes, or on). The monitor is off by default and imposes zero runtime cost when disabled. A dedicated daemon thread (MemoryMonitor) runs on the main Genesis process only (not the thread-service worker). It emits trace lines every 60 seconds at WARNING so they surface in SPCS log mirrors and in worker processes whose GENESIS logger level is WARNING. Process-level trace lines (every 60s):
  • MEM OS: — main-process RSS, Python block count, thread count, open-file count, and (on Linux) cgroup memory trajectory.
  • MEM_LEAK_SCAN / MEM_LEAK_SUSPECT — type-count leak heuristics every 10 minutes.
Per-microapp RSS attribution (every 60s, when GENESIS_MEM_TRACE is on): Microapp processes may run under a dedicated Linux user (microapp_<suffix> via sudo -u on SPCS). Their RSS is not included in the main MEM OS line. Instead, Genesis polls each running app’s PID, sums the RSS of that process and its descendants, and emits one line per app plus a summary: RSS is read via psutil when permitted; on cross-user AccessDenied (typical for sudo -u apps on SPCS), Genesis falls back to the world-readable /proc/{pid}/statm resident-page count. Exited or unreadable processes are skipped without treating EPERM from os.kill(pid, 0) as dead. These lines appear in the Pulse log buffer and are downloadable via GET /api/logs and GET /api/logs/download. Filter with the log WebSocket search field (for example MEM_APP).

Scheduler and background job endpoints

These routes are mounted under /api/scheduler/.... The background-job routes below back Pulse -> Jobs rather than the prompt-job builder on the Scheduler page. GET /api/scheduler/jobs returns success and jobs. POST job-action responses return success and message.

Prompt-job expiration fields

The prompt-job routes under /api/scheduler/prompt-jobs expose the schedule’s expiration and indefinite-run intent. GET responses include these fields in each job, and POST create or PUT update requests pass the expiration values inside the trigger object: For recurring cron and interval schedules, use either a future end_date or perpetual: true with a non-empty perpetual_reason; do not send both. The same validation applies when updating an existing job, including an update that changes only the prompt or name. Invalid expiration input returns HTTP 400 with the validation message in the error response. Pause, resume, run-now, and delete operations do not require new expiration fields. GET /api/scheduler/prompt-jobs/history and DELETE /api/scheduler/prompt-jobs/history can return HTTP 503 while the scheduler restores run history after a restart. Retry after the restore completes.

Background job item fields

Notable FastAPI interval jobs surfaced by GET /api/scheduler/jobs include twin_scan_cost_sync (Twin Scan Cost Sync), which periodically syncs Twin scan LLM spend into SCAN_USAGE_FACTS for the Cost & Usage cube. Control it through App Config (not /api/config/user-config): agent_review_classifier (Agent Review Classifier) runs the scheduled turn-classifier pass when both agent_review.enabled and jobs.agent_review_classifier.enabled are true. When agent_review.steering_patterns.enabled is also true, each completed classifier tick runs a steering-pattern miner post-pass that proposes candidate learnings from recurring corrections (the post-pass is a no-op when either gate is off and never fails the classifier run). Control these settings through App Config (not /api/config/user-config): After classification, each scheduled tick also runs a settlement-only, database-only pass for threads with unsettled task facts that are now dormant. This closes a trailing task that was graded while its thread was still active; active threads remain unsettled until they meet the configured dormancy window. The pass makes no LLM call, and its attempts are separately bounded by jobs.agent_review_classifier.max_threads_per_run.

Internal scheduler subprocess endpoints

These routes are mounted under /api/internal/scheduler/... and are intended for the scheduler MCP subprocess to run dataset, dashboard, and Flow work inside the main FastAPI process. They are not part of the interactive client API.
Requests require an Authorization header with the internal bearer token: Bearer <internal-token> (the same token used by other /api/internal/... routes that accept the ephemeral internal MCP token).

POST /api/internal/scheduler/flows/{flow_id}/run

Response model InternalFlowRunResponse:

Mission Value endpoints

These routes return an instance-wide report over persisted mission, TODO, and BlueprintRun history. A report uses a UTC half-open cohort (from_ts included, to_ts excluded), may cover at most 365 days, and refuses cohorts with more than 10,000 candidate projects. Aggregate metrics cover the complete selected cohort; limit and offset paginate only the mission drilldown. The operation performs multiple persistence reads per request.

GET /api/mission-value query fields

GET /api/mission-value response fields

metrics fields

Mission detail fields

Each missions[] row includes project_id, project_name, work_type, work_type_source, successful, quality, quality_reasons, started_at, output_at, cycle_seconds, lead_seconds, human_wait_seconds, manual_baseline_seconds, and saved_work_seconds. quality is exact, estimated, or incomplete; incomplete observations are counted in quality but are not included in observed_count or aggregate metrics.

PUT /api/mission-value/baselines request fields

The request replaces the whole value; it does not merge individual items. The response contains success and the canonical baselines object. If GENESIS__MISSION_VALUE__MANUAL_BASELINES is set, the replacement is refused with 409; malformed payloads return 400. Invalid cohort parameters and an oversized candidate cohort also return 400. Both routes return 403 when the caller lacks the required permission.

Agent review endpoints

These routes implement agent grading measurement: steering-turn classification, estimate-first backfill, scorecard queries, per-message expectation-met feedback, steering-pattern mining, learning-candidate disposition, adoption-ledger reads, and learning-delta reports. Classification and learning are advisory-only — nothing in these APIs consults a score to allow or deny an agent action. Classifier and backfill candidate scans include hidden threads. The usual eligibility filters still apply, so threads without human turns or with an autonomous origin are not sent for classification. Analysis routes are inert when agent_review.enabled is false (default): Genesis returns 404 for those routes as if the feature were absent. Explicit message feedback remains available while analysis is disabled, so deployments can collect user verdicts before enabling analysis. Scorecard reads and backfill estimate/start require access_pulse_panel when authentication is enabled.

Scorecard and backfill routes

Routes are mounted under /api/agent-review/....

GET /api/agent-review/export/annual

Returns a Markdown (format=md, the default) or PDF (format=pdf) file download. Pass bot_id to generate one agent’s sheet; omit it for the fleet sheet. The annual summary totals are calculated from the union of the covered days in the stored cycle reports and daily rollups, so a day is counted once even when cycle windows overlap after a cycle-policy change. The per-cycle table still lists each stored report, so its rows can repeat covered days; when that happens, the sheet includes an overlap warning explaining the difference. Level-history and decision tables resolve human actor ids to the user’s display name, then email, and finally the raw id when lookup is unavailable. The system and scheduler actors are rendered verbatim. A failed user lookup does not fail the export. Malformed year or unsupported format values return the pinned contract error shape described below rather than a file download.

Agent-grading contract errors

Domain violations from cycle drafting, nomination decisions, and annual export use HTTP 400 or 409 responses with this additive shape:
The machine-readable code remains stable. For example, stale_nomination means a nomination no longer targets the next level after an intervening level change; its message directs the caller to reject it or draft a new cycle. When an agent is demoted or reset, every still-provisional nomination for that agent is terminally marked rejected by system, with a reject event recorded for provenance. This system void does not block a new nomination for the same agent, target level, and cycle: Genesis files the next deterministic attempt id by appending #2, #3, and so on to the base nomination id. The voided rows remain in the append-only history. A nomination rejected by a human remains terminal for that cycle and cannot be re-nominated. Rejecting a stale nomination is also allowed and does not move the agent’s level fold. Weakness-ledger advancement is deduplicated by covered calendar day. A newly drafted cycle advances the ledger only for days not covered by an earlier stored cycle for that agent, so overlapping cycle windows do not recount the same corrections or bank clean days twice.

POST /api/agent-review/backfill/estimate

Returns success, run_id, threads (candidate thread count), and est_cost_usd (estimated USD cost from prompt sizing and model pricing, no LLM calls).

GET /api/agent-review/runs

Returns success and runs (array of run objects).

GET /api/agent-review/scorecard/data

Returns success, bot_id, granularity, series, breakdown, and grading_policies_present. Rate fields in series and breakdown are fractions between 0 and 1 (the web client multiplies by 100 for display). Each series bucket includes bucket_ts (Unix seconds of the period start). First-shot rates come only from grading_policy='full' rollup rows; corrections_only rows never blend into a first-shot figure.

GET /api/agent-review/weaknesses

Returns weaknesses, ordered by stored status (escalated, open, resolved) and then last_seen descending within each status. Each weakness keeps its stored status; display_status is derived from the adoption join. The coaching_adoption_id field is the adoption behind that display status and is the identifier a client can use to focus the Agent Learning adoption ledger on the coaching that applies to the weakness. It is null when no coaching applies.

Weakness item fields

Scorecard filter-options response fields

Scorecard series bucket fields

Scorecard breakdown row fields

Each row aggregates the whole requested range for one (workflow_type, grading_policy) pair. Rows are never blended across grading policies.

Agent review run fields

POST /api/agent-review/backfill/{run_id}/start returns 409 when the run is not claimable (already running or not in estimated status).

Learning routes

Routes are mounted under /api/agent-review/.... They require access_pulse_panel when authentication is enabled and return 404 when agent_review.enabled is false.

GET /api/agent-review/learning-candidates

Returns candidates (array of learning candidate items).

Learning candidate item fields

POST /api/agent-review/learning-candidates/{candidate_id}/accept

Request body: Genesis finds or creates the candidate’s Action Review item under the learning.adopt.v1 action family. The authenticated person decides and applies the item in this request; a policy-allowed item may be applied without a separate human review. The response returns candidate_id, status (adopted), statement_id (wiki learning id when the candidate was anchored; null for unanchored accept-as-summary), adoption_id, and action_review with the applied proposal_id and its final status. Returns 404 when the candidate does not exist. Returns 409 when the candidate is already adopted or dismissed, or when its text differs from the submitted statement. A changed candidate uses error code candidate_changed; nothing is published, so read the candidate again before retrying. While another accept holds the candidate, the response uses candidate_being_accepted and includes held_until; both accept and dismiss are refused. A declined or in-flight Action Review item is also reported as a conflict and includes its action_review details. If an accept may already have published the learning, the response uses 503 with code: "accept_outcome_uncertain", the server-selected next_step, proposal_id, and any anchor_qn or unlocks_at it can determine; do not retry until the named Action Review attempt settles. Returns 403 when policy denies the operation. When that attempt settles, Genesis reads an anchored page back by the exact candidate text and adopts it only when the matching learning landed during that attempt; an older matching learning does not get adopted for this accept and remains for a person to check.

POST /api/agent-review/learning-candidates/{candidate_id}/dismiss

Returns candidate_id and status (dismissed). Returns 404 when the candidate does not exist. Returns 409 when the candidate is already adopted or dismissed, while an accept hold uses candidate_being_accepted with held_until. If a prior accept may have written and its Action Review item is not settled, the response uses candidate_unsettled_in_action_review and includes the holding proposal_id; do not dismiss until that item is settled.

GET /api/agent-review/learning-deltas

Returns deltas (array of learning delta items) and horizon_days (the effective horizon Genesis used for the comparison).

Learning delta item fields

Learning delta window fields

GET /api/agent-review/adoptions

Returns adoptions (array of adoption items), newest first.

Adoption item fields

Expectation-met message feedback endpoints

These routes are mounted under /api/threads/{thread_id}/.... They persist per-user thumbs-up/thumbs-down style verdicts on assistant messages for scorecard hydration and operator review. Authorization is thread-participant based: Genesis derives bot_id server-side from the thread (never from the request) and applies the same thread-access check used by thread read routes. In authentication-disabled deployments, the resolved system_default_user is allowed consistently with those reads. Foreign threads return 403. These routes remain available when agent_review.enabled is false; the flag controls review analysis, not explicit user feedback. For PUT, message_id must identify an assistant answer present in the target thread. Unknown ids and ids for user messages, tool requests, tool results, or system messages return 404; tool requests are not feedback targets even when they render as assistant prose. Genesis trims surrounding whitespace from the requested id before storing the verdict under the canonical id. DELETE applies the same trimming, so a caller can retract its existing verdict even if the message has since been removed from the thread.

PUT /api/threads/{thread_id}/messages/{message_id}/feedback

Returns success and data (a message feedback item).

Message feedback item fields

DELETE /api/threads/{thread_id}/messages/{message_id}/feedback returns success and deleted (true when a row was removed). GET /api/threads/{thread_id}/feedback returns success and items (array of message feedback items for the authenticated caller only).

User settings endpoints

These routes are mounted under /api/auth/settings and read or update the caller’s persisted users.settings JSON object. The web client uses them for per-user preferences such as the default landing page shown when opening Genesis or clicking the logo. When authentication is enabled, Genesis resolves settings for the authenticated user. When authentication is disabled, Genesis serves the persisted system_default_user row (resolved lazily on first read) so local and no-auth deployments can still store preferences.

User settings request and response fields

PUT /api/auth/settings

GET /api/auth/settings returns success and data (the settings object). PUT /api/auth/settings returns success and message.

Default landing page setting

The web client stores each user’s preferred landing page under default_landing_page. When the key is missing, unknown, or the settings request fails, the UI falls back to Launchpad (launchpad). Allowed default_landing_page values:

Blueprint browser preferences setting

The web client stores each user’s Blueprint Library view mode and sort under blueprint_browser_preferences as an object merged through PUT /api/auth/settings. When the stored object is missing, corrupt, or contains unknown values, the web client falls back to tile view and Recently modified sort (updated, desc). Regex search mode is intentionally not persisted.

User settings example

Read the current settings:
Set the default landing page to Missions:

Personal access token endpoints

These routes are mounted under /api/auth/pats....
Personal access tokens (PATs) authenticate protected HTTP requests with Authorization: Bearer gp_.... Genesis treats the PAT as the token owner’s identity on those HTTP routes. WebSocket endpoints continue to use the auth-provider-specific bearer or query-token flows documented in their sections below.Genesis also accepts additional JWT issuers configured in auth.additional_issuers (a map keyed by a human-readable label; matching is on each entry’s issuer URL). Each entry requires issuer and jwks_url, with optional audience (defaults to auth.audience) and identity_source (sub, the default, or email). When identity_source is email, Genesis resolves the caller from the token’s verified email — used by OAuth-proxy identity tokens in dev import workflows. Unknown issuers are rejected; the token signature is always verified against the selected JWKS.

POST /api/auth/pats

With authentication enabled, the caller must be the person’s own sign-in, not a delegated or machine credential. Genesis returns 403 when the caller is an agent, system identity, app-only token, or another delegated credential. A PAT is always owned by the signed-in person; Prover approvals made with that PAT are attributed to the owner and labelled via personal access token.

PAT response fields

POST /api/auth/pats returns success, message, pat, and token. GET /api/auth/pats and GET /api/auth/pats/admin return success plus pats.

Shared PAT record fields

Genesis returns the plaintext token value only from POST /api/auth/pats. Store it securely when created. Later list endpoints return metadata only.

PAT example

Create a PAT from an already authenticated HTTP client:
Use the returned token value on later protected HTTP requests:

Common PAT status codes

Authenticated coding-agent, data, and datasets endpoints

The routes below now explicitly reject unauthenticated callers when auth.enabled is true.

Coding agent session endpoints

These routes are mounted under both /api/cortex-code/... and /api/coding-agent/....

GET /api/cortex-code/sessions/by-thread/{thread_id}

POST /api/cortex-code/{session_handle}/take-control

Thread query-activity endpoint

GET /api/data/bots/{bot_id}/threads/{thread_id}/query-activity

The response is QueryActivityResponse: success plus data, an array of QueryActivityItem objects: Genesis merges durable execution-activity rows with authorized legacy query-result and dataset-cache fallbacks. Partial source failures are logged and omitted independently so available rows still return.

Thread query-result endpoint

GET /api/data/threads/{thread_id}/query-results

Connection metadata browsing endpoints

These routes back the Data Explorer connection tree and other callers that need live database metadata (databases, schemas, tables, columns, and sample rows). They are mounted under /api/data/connections/....

Migration pipeline inspection and scope

These routes support the Launchpad pipeline branch of the database-migration journey. Pipeline inspection reads only the explicitly selected repository files at one immutable revision; it does not execute the pipeline or change the repository checkout. The returned source descriptor contains file paths, SHA-256 digests, byte sizes, and a source_checksum, but not source contents or credentials.

POST /api/data/migration/pipeline/inspect

The inspection is bounded to 32 files, 256,000 bytes per file, and 1,000,000 bytes in total. The response has schema_version: 1, the resolved 40-character revision, the selected files, and source_checksum.

Migration producer discovery and plan proposal

These routes support the migration intake in Launchpad and chat. Producer discovery reads Context Graph lineage and source metadata; plan proposal also binds the choices to an immutable, checksummed scope record. Neither route runs pipeline code.

POST /api/data/connections/{connection_id}/migration-producers

The response’s data includes source, lineage_available, pipelines, unproduced_tables, unproduced_count, and notes. When the Context Graph capability is unavailable, lineage_available is false; this is not a connection error.

POST /api/data/migration/plan-proposal

The response’s data contains target_runtime, available runtime options, objects, target, row_keys, row_key_basis, prover_checks, pipelines, defaults_applied, notes, and the bound scope. The service reads current lineage, source inventory, primary keys, and candidate key-column nullability before saving that scope; chosen dbt projects can add one test-metadata read each. For the chat approval flow, see Migration intake tools.

GET /api/data/connections/{connection_id}/databases

GET /api/data/connections/{connection_id}/databases/{database}/schemas

GET /api/data/connections/{connection_id}/databases/{database}/schemas/{schema}/tables

GET /api/data/connections/{connection_id}/databases/{database}/schemas/{schema}/tables/{table}/columns

GET /api/data/connections/{connection_id}/databases/{database}/schemas/{schema}/tables/{table}/sample

List responses (databases, schemas, tables) include from_cache (boolean) to indicate whether the payload came from the in-process metadata cache. When these routes read a databricks_app_user connection in a Databricks App, a workspace-verified signed-in user’s metadata reads run as that user and bypass the process cache, so one user’s Unity Catalog visibility is never reused for another user. Other connection types and requests without a verified Databricks user are not switched to that user’s token. An empty databases, schemas, or tables array is a successful empty listing, not a connection failure. In particular, a Databricks catalog or database may return no enumerable rows even though the connection answered; callers should preserve the empty explorer state and use a known catalog or database name when they already have one. tables entries are objects with name (string) and type ("table" or "view"). columns entries include name, data_type, nullable, default, and description when the connector reports them. sample returns a data object with columns, rows, total_rows, limit, and offset. If a connector reports reconnect_required because its sign-in is missing or expired, these routes return HTTP 409 with error_code: "sign_in_expired" and a provider naming the platform that needs sign-in. Callers should prompt the user to sign in again rather than treating this as a server failure. SQL Server metadata browsing and query execution are also supported from a Databricks App through the bundled pymssql driver. Its integer DB-API type codes are normalized for column_metadata; pymssql’s BINARY code is reported as a string because the driver also uses it for types it does not identify, including date and time types. See SQL Server connections for TLS and connection-option behavior.
SQL Server CLR types. For SQL Server table samples, geography, geometry, and hierarchyid columns are returned as text so that one unsupported CLR column does not prevent the rest of the sample from loading. The column names remain unchanged. If column metadata cannot be read, the endpoint falls back to the standard SELECT * sample query.
BigQuery mapping. On BigQuery connections, Genesis maps the generic database/schema/table path segments to GoogleSQL objects:GET .../databases returns the connection’s bound project id (via SELECT @@project_id). GET .../schemas lists datasets from {project}.INFORMATION_SCHEMA.SCHEMATA. GET .../tables lists base tables and views from {project}.{dataset}.INFORMATION_SCHEMA.TABLES in one pass (table_type distinguishes views). GET .../columns reads {project}.{dataset}.INFORMATION_SCHEMA.COLUMNS.BigQuery metadata queries route through the shared DatabaseConnector executor (the same path used by query_database and the harvester) so service-account JSON is parsed into credentials_info and user-OAuth connections use a pre-authorized BigQuery client. Project and dataset identifiers are backtick-quoted in generated SQL so dashed project ids parse correctly. Table-preview requests may therefore use a hyphenated BigQuery project id (for example, genesis-471800) as the database value.

Ad-hoc query execution endpoints

Use these routes to execute SQL immediately, store page 1 of the result, and receive a new query_id for later pagination or refresh.
connection_id values on /api/data/... routes accept either the fully qualified type.name form (for example databricks.my_conn) or the bare name stored in legacy CUST_DB_CONNECTIONS (for example my_conn). When a type.name value does not match directly but the segment after the first dot matches a legacy row, Genesis resolves the request to that legacy connection_id before executing metadata or query operations.
Legacy compatibility aliases POST /api/query-results and POST /api/query forward to the same behavior as POST /api/data/query-results.

POST /api/data/query-results

POST /api/data/query-results returns the standard stored query-result payload in data plus these row-limit fields: Genesis validates query-result SQL before it caches the first page. Submit a single SQL statement. Requests that contain multiple SQL statements or obviously concatenated or corrupted SQL return 400 Bad Request.
Newly stored query results are also indexed for find_past_queries(...) recall. This applies in both SQLAlchemy metadata deployments and legacy SQLite-backed metadata deployments; older legacy rows can be backfilled lazily the next time find_past_queries(...) runs.In SQLAlchemy metadata mode, stored query-result reads, writes, and refreshes use the metadata repository, including when the metadata backend is PostgreSQL.

Stored query-result endpoints

These routes are mounted under /api/data/query-results/....
When Genesis uses the datasets-backed query-results backend, stored query results backed by a query-result dataset enforce dataset authorization in addition to connection access rules. GET requests require dataset read access and POST .../refresh requires dataset manage access. App tokens may read only when the token grants dataset read; app tokens cannot refresh these datasets and receive 403 Forbidden.
AG Grid datasource routes such as /api/data-explorer/datasource, /api/data/query-results/{query_id}/datasource, and /api/datasets/{dataset_id}/datasource can return both sql and base_sql in the response body. sql is the wrapped SQL Genesis executed for paging, grouping, or pivoting. base_sql is the original SQL before SSRM wrapping when available. For Microsoft SQL Server connections, Genesis also preserves bracketed identifiers such as [database].[schema].[table] while rewriting paging to SQL Server syntax in sql; base_sql continues to reflect the original submitted query text when available. For Snowflake connections, Genesis parses base_sql with the Snowflake dialect before SSRM wrapping, so VARIANT path expressions such as column:path::TYPE in the original query are accepted rather than causing a parse error during server-side paging, grouping, or pivoting.

GET /api/data/query-results/{query_id}

When the stored query result is backed by the datasets query-results backend, the data object also includes: For stored results, the response includes the stored first-page rows and its rows_per_page. When needs_re_execution is true, page 1 still contains the rows that were stored; clients can re-run the query to retrieve rows beyond that page. A result that cannot be replayed is returned with replayable: false, its stored total_rows, and no rows on later pages. If a connector detected dropped rows but could not observe their exact total, total_rows records a lower bound rather than presenting the stored page as complete. This endpoint returns 403 Forbidden when the caller lacks dataset read access to a datasets-backed query-result dataset, and 404 when the stored query result is not found or has expired.

POST /api/data/query-results/{query_id}/refresh

This endpoint does not document any request body fields.

Transition-contract endpoints

These routes back Data → Migration verification (/data/verify/). Browse, compile, and execute require view_connections. Ratify is the only authority-changing surface: it requires ratify_transition_contract, auth.enabled=true, a genuinely authenticated human caller (not a system or delegated identity), and a configured transition_contract.ratifier_private_key.

POST /api/transition_contract/rules/{rule_hash}/ratify

Successful responses include event_id (registry event of the appended binding) and binding with rule_hash, qualification_event_id, transition_id, ratifier, ratified_at (ISO-8601), public_key_id, and signature (Ed25519 over the binding payload). The ratifier identity comes from the authenticated session, never from the request body.

POST /api/transition_contract/objectives/{objective_hash}/designate-keys

Assert which columns identify rows of one or more source objects. This is a human assertion of row identity — not a measurement from either estate — and it creates a successor objective rather than editing the one named in the path. Refresh the objectives list and compile from the returned objective_hash. view_connections is necessary but not sufficient: the service also requires per-connection access to both estates named by the objective (ownership, manage_all_connections, or a shared grant on each connection). Both source and target are checked. Successful responses include objective_hash (successor carrying the designations), parent_objective_hash (the objective refined), and key_designations (the full designation set on the successor, including inherited ones). Compiled obligation frames expose row identity as key_column (legacy single-column field, null for composite keys), key_columns (composite-capable list), and key_source (declared from the source estate, or designated from this endpoint). Read identity through key_columns when present, falling back to key_column.

POST /api/transition_contract/compile

Returns contract_hash, registry_snapshot_cut, and a one-line summary.

POST /api/transition_contract/execute

Returns run_id, status (running, succeeded, failed, or error), and run (full run record when the run finished within wait_seconds).

Measurement endpoints

These routes back Data → Migration verification measurement tabs (Baselines, Relationships, Compare, Qualifications). Every route below — including reads — requires manage_all_connections. Frozen records and qualification reports can carry per-column minimum and maximum values from customer estates; this is a data-disclosure gate, not a write gate. Agent measurement_tools apply the same permission inside the core. If a measurement tool returns refusal.kind: "fault", the service treats it as an internal measurement-service failure and returns HTTP 500. For a qualification-report read, it does not perform the normal missing-record check or convert the fault into a 404. The response is a generic server-error payload; the exception detail remains in the server log.
GET /api/transition_contract/measure/baselines
List responses include records (not separate baselines / scratch_clones lists), run_index_limit, run_index_truncated (global — older measurement runs were not indexed), and provenance_index_complete (false when the clone/source lookup did not read every report). Each records[] row includes frozen_contract_hash, provenance (qualification_scratch_clone, qualification_source, named_by_measurement_run, or not_established), and optional summary fields (database, schemas, dialect, run_id, qualification_report_hash, and so on). Provenance is never a safety grade.
POST /api/transition_contract/measure/correspondences
Successful responses include correspondence_hash, declared_by (the authenticated session identity recorded on the declaration), and declared (sentences restated for the declarer to check). The request has no declared_by field; a caller cannot attribute the declaration to another person.
POST /api/transition_contract/measure/compare
Successful responses include counts, counts_by_object, counts_by_class, binding_failures, undischarged, and unresolved (integer counts, not lists), indeterminate_regions (strings), object_pairing, exemplars, exemplars_capped_at, coverage, reading_guide, and value_agreement_is_not_discharge.

Salesforce merge endpoints

These routes govern irreversible Salesforce Lead, Contact, and Account merges. They are compatibility aliases for the provider-neutral CRM merge endpoints with connection_type=salesforce and route new proposals through Action Review. Prefer the /api/crm/salesforce/merge/... paths for new integrations. Every route below — including reads — requires approve_agent_action. That permission is deliberately not named for merges: it grants the right to sign off on an action an agent cannot authorize for itself, and a CRM merge is the first of those rather than the only one. Preview responses carry record-level values from the customer’s Salesforce org, and stored approvals retain that preview verbatim. List and approve operations are additionally scoped to connections the caller can access. The approval routes are the authority-changing surfaces beyond the permission gate: they require auth.enabled=true and a genuinely authenticated human caller (not a system or delegated identity). Agents can preview, submit a proposal, and execute a merge through salesforce_connector, but they cannot approve an Action Review proposal on behalf of a human. Merge actions refuse when the target connection’s locator does not set merge_enabled to a truthy value, when merge_survivorship_policy is missing or invalid, or when Genesis cannot verify the Salesforce org id at authentication.

POST /api/salesforce/merge/preview

Successful responses include projection (canonical merge preview) and digest (sha256 of the projection, re-checked at approval creation and execution).

POST /api/salesforce/merge/approvals

Uses the same body fields as preview, plus: Successful responses include approval_id, the effective Action Review status, digest, expires_at, and review_required. When review_required is true, a human decision is required before execution.

POST /api/salesforce/merge/approvals/{approval_id}/approve

No request body. The approver identity comes from the authenticated session only. Successful responses include approval_id, status, and approved_by.

POST /api/salesforce/merge/approvals/{approval_id}/execute

Successful responses include approval_id, status (executed), and result with survivor_id, merged_ids, updated_related_ids, and success.

GET /api/salesforce/merge/approvals/{approval_id}

Returns data with the approval row, including preview (stored canonical projection), execution_result when present, and status fields such as pending, approved, claimed, executed, failed, expired, or rejected.

CRM merge endpoints

Provider-neutral routes for irreversible CRM merges. Set connection_type to salesforce or hubspot. Every route below — including reads — requires approve_agent_action. That permission is deliberately not named for merges: it grants the right to sign off on an action an agent cannot authorize for itself, and a CRM merge is the first of those rather than the only one. Preview responses carry record-level values from the customer’s CRM org, and stored approvals retain that preview verbatim. List and approve operations are additionally scoped to connections the caller can access. The approval routes are the authority-changing surfaces beyond the permission gate: they require auth.enabled=true and a genuinely authenticated human caller (not a system or delegated identity). Agents can preview, submit a proposal, and execute a merge through salesforce_connector or hubspot_connector, but they cannot approve an Action Review proposal on behalf of a human. Supported connection_type values: salesforce, hubspot. Merge actions refuse when the target connection’s locator does not set merge_enabled to a truthy value, when merge_survivorship_policy is missing or invalid, or when Genesis cannot verify the provider tenant identity at authentication (Salesforce org id or HubSpot portal id).

POST /api/crm/{connection_type}/merge/preview

Successful responses include projection (canonical merge preview) and digest (sha256 of the projection, re-checked at approval creation and execution).

POST /api/crm/{connection_type}/merge/approvals

Uses the same body fields as preview, plus: Successful responses include approval_id, the effective Action Review status, digest, expires_at, and review_required. When review_required is true, a human decision is required before execution.

GET /api/crm/{connection_type}/merge/approvals

No request body. Returns data as a list of pending approval rows for the specified provider. Before selecting rows, Genesis performs a best-effort, provider-scoped recovery of stale claims left by an interrupted process. The recovery runs on demand when the approval ledger is read, at most once per cooldown window; idle instances do not run a recurring recovery job. Claims older than salesforce.merge_claim_stale_after_seconds or hubspot.merge_claim_stale_after_seconds (both default to 900 seconds) are marked failed with an unknown remote outcome rather than retried.

GET /api/crm/merge/approvals

No request body. Returns data as a list of visible approval rows across all registered CRM providers and server_time (server clock used to evaluate expiry). Rows for connections the caller cannot access are omitted. Genesis attempts the same on-demand stale-claim recovery for every registered provider before selecting rows; a recovery failure for one provider does not prevent another provider’s attempt or the list response.

POST /api/crm/merge/approvals/batch-approve

Each item in approvals: Successful responses include outcomes — one entry per submitted approval in submission order. Each outcome is either a success (approval_id, status, approved_by) or a failure (approval_id, error_code, message). error_code is one of bad_request, forbidden, not_found, conflict, service_unavailable, or internal.

POST /api/crm/{connection_type}/merge/approvals/{approval_id}/approve

No request body. The approver identity comes from the authenticated session only. Successful responses include approval_id, status, and approved_by.

POST /api/crm/{connection_type}/merge/approvals/{approval_id}/execute

Successful responses include approval_id, status (executed), and result with survivor_id, resulting_record_id, merged_ids, updated_related_ids, warnings, and success. If the provider refuses the one merge request, the response records status: not_applied and preserves the provider’s error detail. If the request was sent but the outcome cannot be proved, the approval remains reconciliation_required; do not retry it, and inspect execution_result or the approval detail for the provider’s error when present.

GET /api/crm/{connection_type}/merge/approvals/{approval_id}

Returns data with the approval row, including preview (stored canonical projection), execution_result when present, and status fields such as pending, approved, claimed, executed, failed, expired, or rejected.

Estate (source-contract) endpoints

These routes back the Estate workspace (named estates with scan timelines, source-estate characterization, contracts, jitter qualification, system-diff remediation, governance (Δ authoring, waivers, ranked findings, attestations), migration validation, the Genesis Knowledge System corpus, the estate ruling library (tier-3 ambiguity overlay), the Adjudication Court (ranked docket queue, attorney chat provenance, improvement-review queue, and human or headless decide/adopt), issue dossiers, and Π rule-review workflows). Submit-and-return routes stop accepting new flows once Genesis begins shutdown. If a run row was allocated before that refusal, Genesis closes it before returning the scheduling error; poll GET /api/estate/runs/{run_id} to see the terminal state rather than treating the request as an in-progress run. Estate review and approval reads are scoped to the caller’s estate visibility as well as view_connections. A comparison-scoped read treats an unknown or unreadable comparison as not found. Lists omit findings and approval records tied to comparisons the caller cannot see, and calculate any totals, waiver counts, or rule-usage counts after that visibility filter. This applies to the comparison review, attestation, waiver, export, correspondence-rule, finding-disposition, delta, ruling, and package reads below; approval work on a hidden comparison is not disclosed by a row or count. Browse, define and scan named estates, characterize, mine, compile, verify, jitter, system-diff run/read, on-demand fault-drill, fault-drill reads, and behavioral reads, knowledge corpus read/collect, Δ preview/propose, attestation read, witness drill-down, collaboration notes, comparison Human Review workflow persistence, reconciliation episodes (import/read, server-owned open/round/close lifecycle, live derivational open/round streaming from graded obligations, machine-channel issue replies and prover adjudication, human-court finding rulings, durable-finding cross-episode history, and the cross-run finding lifecycle ledger), programmatic Contract verification (POST /api/estate/verifications), and immune-arsenal ranking require view_connections. Agents may author and propose Δ artifacts through those routes; only ratification changes authority. Comparison Human Review is an annotation layer over a comparison: per-finding diagnosis, disposition, owner, workflow, and discussion remain available to view_connections reviewers. Review doors use one signer rule: with authentication enabled, a genuinely authenticated human signs and a caller-supplied name is refused; with authentication disabled, an allowed caller may submit and is recorded as the name in X-Genesis-Signed-As, disclosed as <name> (self-reported, auth disabled), or as local operator (auth disabled) when the header is absent. Finding and family disposition, remediation-request filing/dispatch, and collaboration notes are annotation acts over the findings ledger (nothing closes a finding — only re-verification does). They require ratify_source_contract (collaboration notes require only view_connections). With auth.enabled=true, a genuinely authenticated human is required; when auth is disabled, writes are permitted and attributed to local operator (auth disabled) so diagnosis and remediation surfaces stay usable on local stacks. Authority-changing surfaces — candidate adjudication, contract sealing, ruling-library settle / ratify / unsettle / answer (the tier-3 ambiguity overlay), Adjudication Court decide and adopt (POST /api/estate/court/dockets/{docket_id}/decide and .../adopt — each emits a ruling into the library) — require ratify_source_contract, auth.enabled=true, and a genuinely authenticated human caller (not a system or delegated identity). Π/Δ ratification, correspondence-rule review and revoke, and Π rule-review confirm/revoke use the signer rule above. Ruling-library and court reads (GET /api/estate/rulings, GET /api/estate/rulings/{ruling_id}, GET /api/estate/ruling-questions, GET /api/estate/court/dockets, GET /api/estate/court/dockets/{docket_id}, GET /api/estate/court/dockets/{docket_id}/audit, and GET /api/estate/improvement-docket) require only view_connections — agents and customers may browse the library and docket queues as living documentation. The Court panel shows the estate-scoped ambiguity queue separately from the all-estates improvement queue. Court attorney-chat recording (POST /api/estate/court/dockets/{docket_id}/chat) also requires only view_connections; attribution comes from the authenticated caller (auth-off callers are labeled local operator (auth disabled)). Spec-conformance compile, reading consult (POST /api/estate/spec-conformance/consult-reading), agentic spec-author seat (POST /api/estate/spec-conformance/author-seat), and grade (POST /api/estate/spec-conformance/compile, POST /api/estate/spec-conformance/grade, POST /api/estate/spec-conformance/grade-compiled, POST /api/estate/migration-envelope/grade), estate preflight (POST /api/estate/estates/{estate_id}/preflight), estate archive (POST /api/estate/estates/{estate_id}/archive), and stimulus runs (POST /api/estate/stimulus/run) also require ratify_source_contract — authoring or sealing the spec an envelope is graded against, commissioning an agent mission for readiness or behavioral verification, or injecting into clone estates is the same authority class — but are system-triggerable assessments with no genuine-human gate (a permitted service or agent caller may invoke them). The Estate nav is shown by default; it is hidden only when show_estate is explicitly false on GET /api/config/frontend (set AppConfig features.show_estate to false, or GENESIS__FEATURES__SHOW_ESTATE=false to force-lock).

Prover result visibility

view_connections is also the per-result visibility boundary for Prover data. A principal may see an estate only when they may see every connection in that estate; a comparison is visible only when they may see every connection behind both of its estates. This applies to agent tool calls as well as REST requests: an agent acts with the authenticated user’s connection reach and does not widen it. Direct connection scans without an estate are authorized from the connection recorded on the scan’s run. A result whose producing connection cannot be resolved is hidden rather than treated as public. List responses report filtering instead of silently presenting a partial board. The hidden field is a count of withheld rows: it is a total for estate, snapshot, ranked-finding, and unpaged result lists; it is per page for system-diff, findings, ledger, and comparison-event pages. For system-diff, findings, and comparison-event pages, has_more and next_cursor describe paging on the unfiltered keyset, so continue paging and sum per-page hidden values when a total is needed. The findings-ledger page instead applies visibility before cutting the page; its has_more and next_cursor walk only admitted rows, and hidden counts withheld rows in that page’s span. The findings-ledger summary applies the same visibility predicate inside its aggregation, so its open_total, by_state, by_layer, by_pair, and pair_unresolved values already describe only visible findings. Detail reads return 404 when the requested snapshot, estate, or comparison is hidden; a comparison requires both endpoint estates to be visible. The same visibility rule gates Prover approvals, run-level verdicts, and exception grants: comparison-scoped actions require the caller to be able to open that comparison. Consult ruling actions require the caller to be able to read the finding. A hidden subject returns the corresponding read’s 404, rather than disclosing whether an approval target exists. Write and submit routes apply the same check before writing or starting work. Estate archive and visibility changes, code-asset operations, drills, and artifact reproduction require the named estates to be visible; candidate adjudication requires visibility to the snapshot the candidate came from. Grades check their named snapshots, comparison, and estate; stimulus runs check their named snapshots and comparison. Boundary characterization and contract delta operations check the referenced boundary or contract. Run cancellation checks the run’s comparison, estate, and connection before it writes. Hidden or unresolved subjects answer as unknown (404), before the action proceeds. Spec-conformance prose compilation is the exception: a snapshot the caller cannot see is omitted from grounding, and the compiler proceeds without that snapshot’s inventory. This boundary also applies to routes and agent tools that resolve or act on comparison records: hidden comparisons and findings are treated like unknown ones. Family rollups for a comparison fold only findings the caller can see, so hidden members do not affect the returned counts or samples. For records that cannot be placed under a visible comparison or boundary, a principal with manage_all_connections may see the otherwise unplaced record; other principals continue to receive the hidden or unknown result. The same subject check now covers grading-run adjudication, stored adjudication reads, finding consultation, reconciliation episodes, and estate ruling and court tools. A run is visible through its comparison when it names one. Otherwise a recorded estate must be visible and a recorded connection must also be visible; with no estate, a recorded connection is checked on its own. A proceeding is visible through its comparison, or through every recorded conformance connection; an unplaced proceeding requires manage_all_connections. An episode without a comparison requires a visible estate and, when the estate’s recorded snapshots identify it, a visible connection; with no estate, its recorded connection is checked alone. Estate ruling and court records are available when a visible comparison touches the estate, or to a principal who can manage every connection. The same caller-specific rules also apply to court docket and audit reads, improvement-docket entries, transition detail, family and behavioral dossiers, proofless-grade review, reading-consult and seat status polls, and live run subscriptions. Lists and embedded rows are filtered to visible subjects; the endpoint descriptions below call out where filtering changes paging or returned counts. Hidden identifiers use the same unknown-style response as missing records, except where an endpoint documents an empty or otherwise safe result. Run feeds and polls use a caller-specific scope for every subject a run names: all recorded estates, its recorded connection, and every comparison in its summary must be visible. Run connection stamps and operator fallback are described in run ownership and connection stamps. A run naming none of those subjects is judged by its boundary: every snapshot stored under that boundary must be visible, or, when it has no snapshots, its registered connection must be visible; if neither snapshots nor a registered connection identify it, only a principal with manage_all_connections may see it. A run bound to nothing is visible to its recorded operator or user principal, or to a principal with manage_all_connections. GET /api/estate/runs and the active-run feed omit inaccessible runs before pagination and counts; polling one returns 404, indistinguishable from an unknown run. Comparison- and estate-scoped side reads also apply caller visibility before resolving or aggregating results: hidden subjects use each endpoint’s existing no-result response (404, null, an empty list, available: false, or unresolved_comparison on stimulus preflight), and family-member counts include only visible findings.

Prover listing response timings

The completed listing reads below return a Server-Timing response header. Timing is enabled automatically; there is no configuration key or request parameter. The header contains one member for each stage that ran, formatted as name;dur=<milliseconds> and optionally ;desc="<unit>=<count>", followed by total;dur=<milliseconds> for the read service’s recorded window. The window covers the service’s reads, resolutions, and projections, but not response encoding, middleware, network transfer, or client-side timing. An error response does not include a stage table. The stage vocabulary is route-specific: The count description is omitted when a stage does not report a count; an omitted count does not mean zero.

Clarification review and reopened verdicts

In clarification responses, conversation_with identifies the signed-in person by email or display name rather than a bare user id; it is unnamed when no name can be resolved, unauthenticated when authentication is disabled, and absent when no person was present. Clarifications are append-only agent records that a sealed specification may cite. Review is a separate, after-reliance action: a person may ratify a recorded clarification before or after a seal cites it, but nothing waits for that review. Ratification changes its current status to ratified, sets provenance_kind to person, and names the reviewer. A person can overturn a clarification, changing its current status to overturned; the agent that recorded an agent-certified clarification can also withdraw it on the owner’s word, changing its status to withdrawn. Neither action deletes or mutates the record. Overturned and withdrawn clarifications are refused by later spec authoring, so new seals cannot rely on them. Agents withdraw through the withdraw_clarification tool; the review routes below remain person-only. When an agent records or withdraws a clarification through POST /api/tool_call, include agent_id to run under that agent persona. The stored recorder identity then names the agent that acted, rather than attributing the record only to the authenticated caller; see the tool_call request fields. For the record_clarification tool result fields and repeated-ruling behavior, see its tool reference. The review identity is resolved by the server, never accepted from the request body. With authentication enabled, the review routes require ratify_source_contract and a genuinely authenticated human; agent-delegated tokens and service accounts receive 403. With authentication disabled, the review actor is recorded as local operator (auth disabled). Agent-made clarification records separately expose conversation_with: the signed-in person whose conversation the turn was recorded in, or unauthenticated when authentication was disabled. The optional request body is { "note": "..." }; the note is stored on the review event. Unknown ids return 404, and a second review of an already reviewed clarification returns 409. Every seal and run reads the current clarification state when requested. An overturn or withdrawal retires the clarification: a compiled-spec view exposes a reopened seal through who_decided.reopened, with the earliest cause at the top level and all causes in causes. A run that is a verdict over that seal exposes the same notice in reopened and projects outcome to not_verified, regardless of the stored outcome. This applies to GET /api/estate/runs, GET /api/estate/runs/{run_id}, and the run wake/read surfaces; the spec_author_seat run that produced the seal is excluded. Run listings and complete run reads include the common progress block, next_step sentence, and next call descriptor. next is {verb, args, why}; its verb is null when no call by the current caller moves the work along. See Prover run progress for their shared semantics and polling guidance. Every terminal run also carries outcome beside status in the envelope and listing row. outcome is one of PASS, FAIL, INDETERMINATE, not_verified, no_findings, or sealed; it is null while the run is non-terminal. status answers whether the process finished, while outcome answers what it decided. See The status is never the verdict for the meaning of each value and the fail-closed cases. For a grade, a not_verified verdict with a non-empty blocking list is published as FAIL, because the grade measured the migration and found a definite blocking result; an empty blocking list remains not_verified. The run envelope always includes report_this, a string or null. For terminal proof (prove) and grade (spec_conformance) runs, it is the sentence to report from the stored run summary: successful results state the verdict and obligation tally, name up to ten failing obligation ids (counting any remainder), and include available unmet scope, historized objects whose history was not tested, comparison counts, and full run, grade, comparison, and spec ids. A successful proof with no spec says that no obligation was checked. When a proof’s comparison ran under an assumption withdrawn for its estate pair, the sentence names each withdrawal (who withdrew it, when and why) and how many readings it moved, read from summary.comparison_counts.withdrawn_assumptions. A terminal proof or grade that did not succeed says it verified nothing and includes its stop_reason when available. The field is null while a run is live and for run types that grade nothing. See reporting a completed run for how agents use this sentence, including the row counts attached to named failed checks. See Prover run semantics. For repair-run settings as presented on run reads, see Prover run provenance. Each complete run’s envelope also includes grade_engine_version and reused_from_grade_version. grade_engine_version is the grading-code version recorded by the grade it carries, read from the grade summary (including summary.summary when a proof carries the grade there), or from row provenance when the grade has not written its summary yet. reused_from_grade_version names the version of a grade carried over rather than run for this answer: the grade submission reused, the proof reused, or a new proof reused its grade child. Both keys are always present; grade_engine_version is null when the run carries no grade or no version was recorded, and reused_from_grade_version is null when no grade was reused or its version was not recorded. For a reused verdict, grade_engine_version identifies the grader behind that verdict, not the serving build. Run envelopes also expose the same published and withheld headline fields on their summary object. Migration-validation run summaries include output_relations, mapping each output to the source and target relations recorded by its traces. A side is omitted when its trace records no relation for that output. Prover comparisons use this evidence to scope a rewritten-view license to the pair tested by the run; see fresh licensing evidence. For GET /api/estate/runs, shape=full and shape=card summaries add published and withheld headline fields for each comparison count present: unlicensed_published / withheld_unlicensed and indeterminate_published / withheld_indeterminate. The withheld fields are null when nothing was withheld. Summaries with no corresponding comparison count gain no fields, and shape=slim omits them. The server derives these fields for both older and newer stored summaries. For POST /api/estate/spec-conformance/author-seat, repository and chat-upload asset forms, including the ref requirement, are documented in the author_spec tool reference. Terminal writes are conditional: once a run row has a terminal status, a later completion does not replace its status, finished_at, or terminal summary. This preserves a stale-run reconcile, refusal, failure, or cancellation when work finishes after the row was closed. The late arrival is retained in the row’s summary instead: summary.late_completions is an oldest-first list capped at 20 entries, and summary.late_completion_count counts every late arrival without that cap. Each list entry contains status_attempted, an ISO-8601 UTC at timestamp, a basis describing the terminal state that won, and the attempted summary_attempted document (or null). An at-least-once retry with the same terminal status and summary is treated as already recorded and is not added again. Heartbeat updates after a row is terminal are ignored. The run envelope also declares these values as late_completions and late_completion_count, always present even when they are an empty list and 0, so clients can distinguish “no late completion” from an older server that does not expose the fields. The run’s own summary remains authoritative: these envelope fields report what a refused terminal writer attempted, not a replacement verdict. All run-envelope timestamps (started_at, finished_at, heartbeat_at, provenance stamped_at, and late-completion at) are ISO-8601 strings with an explicit timezone offset when the stored value is a valid ISO timestamp. A malformed stored timestamp is served verbatim and logged at WARNING rather than being assigned an invented offset. When stale-run reconciliation reaps a still-running row, it preserves the driver’s existing summary and adds the liveness account under summary.reaper. That block includes reaped_at and a basis naming the measured liveness evidence and the estate_char.reconcile.stale_after_seconds threshold that was applied; the existing top-level error and error_type remain available for readers that already use them. If the stored summary is not a JSON object, the raw value is retained in summary.reaper.unparsed_summary rather than discarded. The reaper only closes a row that is still running when it writes, so a completion that wins the race is not rewritten as abandoned.

Readiness checklist conversation provenance

POST /api/estate/readiness and GET /api/estate/readiness/{readiness_id} include these fields in data: These fields describe provenance, not answer content; an identical submission from a later conversation keeps the first record’s recorded_in_thread.

Transition surface

A transition groups comparison tracks into one migration effort and exposes its history through a portfolio and a detail view. transition_ref accepts either the opaque transition_id or the stable lowercase-hyphenated slug.

GET /api/estate/transitions

The portfolio returns one data item per transition, ordered newest first. Use the optional status query to filter by active or closed; the endpoint does not apply a default limit.

GET /api/estate/transitions/{transition_ref}

The detail response contains data with the transition’s edges, all generations in oldest-first order, pins for the current generation, and derived counts. Edges are included only when every named estate is visible (an edge naming no estate is shown only to a principal with manage_all_connections). Pinned comparisons and runs are included only when the caller may see them. edge_count and station_count are calculated from the visible edges; station_count counts distinct estates touched by those edges. sealed_spec_count counts distinct sealed specifications cited by the current generation. Each edges item includes track_id, position, source_role, target_role, the source and target estate identifiers and dialects, the track status, and the mediation set: platform_pack, schema_spec_hash, or both. When both are present, they describe one hop that changes platform and schema; they are not two separate edges. Each generations item includes: The current_generation_pins items contain pin_kind, subject, pin_ref, and an optional label. Valid pin_kind values are spec_seal, effective_view, diff_of_record, run_of_record, register_commit, closing_record, and archived_board.

POST /api/estate/transitions

The creating identity is taken from the authenticated request context, not from the body. The response returns the newly created transition detail.

POST /api/estate/transitions/{transition_ref}/generations/{generation_number}/close

This endpoint closes the named generation and returns its data record. Closing is append-only: the request records closed_at and the authenticated caller as closed_by, and a second close of the same generation returns 409. Each pins item has the following fields: Pins on a closed generation cannot be added or changed later. A missing transition or generation returns 404; malformed citations return 400; a conflicting or already-closed record returns 409.

POST /api/estate/transitions/seed/etfjpx

Seeds the recorded ETFJPX on Databricks lineage. The first call creates one transition with three comparison edges and three generations (V17, V18, and Transition v1); V17 and V18 are archived committed-board records, while Transition v1 is the current native generation. A repeat call is safe and returns the existing detail with created: false; a first successful call returns created: true.

POST /api/estate/characterize

The optional Idempotency-Key request header names the submission when a caller needs to distinguish an intentional repeat from an accidental duplicate. While an identical characterization is queued or running, Genesis returns the existing run_id with reused: true and reused_basis instead of starting a second scan. A distinct header value forks the submission identity and permits a second concurrent scan on purpose. A repeat after the first scan finishes always starts a new scan because characterization measures the estate at an instant; failed or cancelled runs likewise do not satisfy a repeat. A new scan returns reused: false and reused_basis: null. The caller’s resolved identity governs interrupted characterization resume; see Prover identity and resume isolation for the boundary. For dialect: "palantir_foundry", database is the Foundry space RID or display name, and schemas contains folder paths relative to that space (or folder RIDs). Dataset objects are addressed by their Foundry dataset RID rather than display name; the catalog provider resolves those RIDs and the measurement queries run through Foundry’s Spark SQL surface. Omit schemas for the full-source scope. See Palantir Foundry integration for connection setup and Genesis Prover for the measurement model. Each as_of_window_rules item is a strict pass-through of the estatechar AsOfWindowRule shape — only object_name, anchor_column, lower_bound, and upper_bound are accepted. Bounds must be fixed ISO date or timestamp literals (not wall-clock functions such as current_date); when both bounds are present, lower_bound must not be after upper_bound. A malformed rule (missing bounds, non-ISO literal, reversed bounds, or unknown key) returns 422 before the background scan starts. Each provenance_assertions item is a proposal, not a client-supplied attribution or license. The server ignores attribution fields, stamps the authenticated caller, and verifies the source column against the live catalog during the scan. A proposal cannot override an existing declared foreign-key origin; a malformed wire shape returns 422, while a proposal rejected by the shared provenance rules returns 400 before a run is created. Each assertion adds one catalog query during the scan, and a catalog contradiction fails the scan rather than silently applying the relationship. For object-level scope, each name must be schema-qualified and spelled as the catalog spells it, except that matching ignores letter case. Blank names, unqualified names, and names with empty or edge-whitespace components return 422 before the background scan starts. If a non-empty objects list includes an object that is not present in the catalog or is not visible to the scanning role, the run fails instead of silently characterizing a smaller scope. Set allow_absent_scope when an additive greenfield spec intentionally names relations that the build will create: names absent from a visible catalog are then reported in the completed run’s summary.scoped_absent, alongside summary.catalog_rows_seen, rather than failing the run. For a builder-created target schema, when the characterization also took a whole-database listing, the completed summary includes summary.listing_rows_seen and its corresponding summary.listing_provenance so the row count remains attributable to that listing. These fields are omitted when no listing was taken. The premise still requires the object pin and a visible catalog; an empty estate remains an error. For a target scan, allow_absent_scope: true without absent_scope_basis returns 422 rather than creating a snapshot. Use absent_scope_basis: "relocation" when the target is the not-yet-moved round-0 side of a declared relocation, or "frozen_build_did_not_produce" with its complete build_evidence when the build failed before producing the declared relations. The declared basis is content-addressed with the observation boundary and is carried into proof summaries so an element over a relation measured absent can be identified as unbuilt rather than as a failed read. See Prover run results for how proofs present that distinction. Submit-and-return: the response returns immediately with run_id, top-level status: "running", and data containing { "success": true, "run_id": "...", "status": "running" }. The scan and chained contract compile run as a background task; snapshot and contract fields land later via the runs/snapshots feed. On source scans, the static fault-drill coverage meter runs on the same background run only when estate_char.fault_drill.run_on_scan_default is true (schema default false); otherwise the terminal summary carries fault_drill_skipped and operators use POST /api/estate/estates/{estate_id}/fault-drill to drill deliberately. Follow progress live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}.

GET /api/estate/discover-schemas

Successful responses include schemas (sorted list of selectable non-system schema names; engine-system schemas such as information_schema, pg_catalog, ClickHouse system, or SQLite temp are excluded per dialect, matching full-source characterization scope) and error (null on success, or a human-readable message when discovery could not run). For SQLite, the returned schema names are the databases attached to the connection; the per-connection temp database is excluded because its objects are non-durable. Discovery opens the saved connection as the authenticated human caller, not as a bot identity. A personal connection therefore works when that caller owns it and can read it; the route does not substitute a bot identity for the authenticated owner. Discovery is best-effort: a bad connection, unsupported dialect, or query failure returns schemas: [] with error set (not HTTP 500) so the scan form can fall back to free-text schema entry instead of blocking a characterize request.

GET /api/estate/estates

Successful responses include data as a list of named estates, each with a scan rollup (scan_count, latest_scan) for the landing. scanned_snapshot_ids lists every snapshot bound by that estate’s scans, sorted; it is [] when none of its scans bound a snapshot. Every row also carries archived (boolean, default false) — the landing SOURCES grid visibility hide, distinct from lifecycle archive (active: false, status: 'archived'). Each row carries origin_label: null for an estate registered by an operator, or a sentence identifying a startup-backfill estate whose id begins estate_bf_. Set or clear the visibility hide with PATCH /api/estate/estates/{estate_id}/archived.

POST /api/estate/estates

Two request shapes are accepted (both role-agnostic):
  • Crafted / multi-member — pass members (one or more (connection, database, schema-scope) rows, possibly across several connections) and optionally code_assets (git ETL repos).
  • Flat single-member shim — pass connection_ref, dialect, and database at the top level (plus optional schemas / excluded_objects) and omit members. Normalized into a one-element member list.
* Provide a non-empty members list or all three flat shim fields (connection_ref, dialect, database). Registration is idempotent by scope. If an active estate visible to the caller already covers exactly the requested member scope — the same connection, database, schema list, and excluded objects for every member — the endpoint returns that estate unchanged with data.reused: true; it does not validate, save, or start a preflight for this request. The existing estate’s name, role, kind, source_only, and code assets win, so callers should use the returned estate_id or update that estate explicitly. A different schema list or database is a different scope. If the matching estate is not visible to the caller, the endpoint returns 409 without naming it or creating a second estate. A newly created estate returns data.reused: false. At backend startup, Genesis runs an idempotent backfill for legacy duplicate scopes. It considers live, non-platform estates created strictly before September 24, 2026 at 06:26:26 UTC with the same stored connection, dialect, database, schema pin, and excluded objects. The oldest registration (created_at, then estate id) remains live; younger matches are archived, not deleted. Include archived rows with GET /api/estate/estates?include_archived=true. For a new estate, define-time validation runs before save: every member connection is checked. All reachable → status: "validated"; any unreachable → status: "partial" with each error recorded. Nothing is scanned yet — call POST /api/estate/estates/{estate_id}/scan separately. Overlap on any underlying database is allowed when the scope differs. When another estate already fronts the same physical (dialect, database) boundary, the save still succeeds but data includes siblings_over_same_database (an empty list when none exist) and siblings_basis. The disclosure is not a refusal: use the returned estate_id to address the intended estate explicitly, or archive an accidental duplicate. The registration response also includes data.same_source_estates and data.same_source_basis. On SQLite, these identify every other live, operator-registered estate using the same connection’s file, regardless of its registered database name; on other dialects, the source is the connection and database. These estates remain separate and their proof histories are not merged or refused. When another estate reads the source, each entry in same_source_estates contains estate_id, name, connection_ref, database, and a proof_history object with proved and latest_proof_run_id. The registering estate’s data.proof_history has the same shape. The latest run is the newest prove or standalone system_diff proof; latest_proof_run_id is null when none exists. If no other live estate reads the source, same_source_estates is empty and data.proof_history is null. same_source_basis summarizes which estates have proof history. On SQLite, finding these estates costs one narrowed estates query; when any others are found, the response also reads proof history for each one and for the registering estate (two indexed run reads per estate). When estate_char.preflight.enabled and estate_char.preflight.auto_on_save are both true (schema defaults), Genesis also fires a non-blocking post-save preflight run in the background after create — the save response returns immediately while readiness lights accrue in the live feed and GET /api/estate/estates/{estate_id}/preflight/latest. Successful responses include estate_id and data with the persisted estate (payload.connections holds the full member list; denormalized card columns reflect the primary member — members[0]). The operator identity is taken from the authenticated session, never from the request body. The response also always includes data.unpinned_code_assets (string[]) naming attached assets that have not been cloned or had a Databricks revision captured, and data.source_only_suggestion (object or null). The suggestion is present only for a newly registered role: "source" with no code assets and source_only still false; it gives the update_estate(..., source_only=true) call for raw data. Attaching no code is not inferred to mean raw data.

PATCH /api/estate/estates/{estate_id}

Two mutation modes coexist (only passed fields change):
  • Scope-only edit (legacy): pass schemas / excluded_objects to re-pin the first connection’s scope in place.
  • Full members[] + code_assets[] replacement (ISS8): pass members to add / remove / edit sources wholesale — every member is re-validated, payload.connections and payload.validation are rebuilt atomically, and denormalized card columns follow members[0]. When members is passed, scope-only fields are ignored. Pass code_assets to replace attached ETL repos ([] clears them). Estate edit payloads typically omit server-generated clone metadata; Genesis preserves a stored clone when the incoming asset omits clone and source identity is unchanged (first non-blank of bot_git_repo, url, or connection_ref, plus trimmed ref and subdir — whitespace-only edits on those fields do not count as a change). Changing the source drops the stored clone; an incoming asset that explicitly includes clone is written as-is. Omit members to leave existing connections untouched.
When estate_char.preflight.enabled and estate_char.preflight.auto_on_save are both true (schema defaults), Genesis also fires a non-blocking post-save preflight run in the background after a member or code-asset replacement — the patch response returns immediately while readiness lights accrue in the live feed and GET /api/estate/estates/{estate_id}/preflight/latest. The patch response also always includes data.unpinned_code_assets and data.source_only_suggestion with the same meanings as the registration response. Because an edit has no registration role, source_only_suggestion is null on edits; use update_estate(source_only=true) explicitly when the estate is raw.

POST /api/estate/estates/{estate_id}/archive

Archive is the estate lifecycle’s delete: the row is never hard-removed (scan timelines, snapshots, and comparisons still reference it). Genesis sets active=0 and status='archived', which drops the estate from GET /api/estate/estates (unless include_archived=true), target-candidate pickers, and GET /api/estate/system-diffs (unless include_archived=true) — archiving an estate retires its comparisons from the live grid. Re-archiving an already-archived estate is idempotent — HTTP 200 with already_archived=true and no second state change. This is not the landing SOURCES visibility hide — use PATCH .../archived for a reversible card hide that leaves active/status untouched. Successful responses include estate_id, archived (always true on success), already_archived, and data with the persisted estate row.

PATCH /api/estate/estates/{estate_id}/archived

Set or clear the landing SOURCES grid visibility hide on a named estate. Two distinct “archive” concepts exist:
  • Lifecycle archive (POST .../archive): soft DELETE — sets active=0 and status='archived', requires ratify_source_contract, and retires everything downstream (landing cards, target candidates, comparison participants).
  • Landing visibility hide (this route): cosmetic and reversible — archived=true hides the source card from the landing’s default SOURCES view; archived=false restores it. Never changes active or status; the estate stays a live target candidate and comparison participant.
Successful responses include estate_id, success, and data with the persisted estate row (including top-level archived).

POST /api/estate/estates/{estate_id}/validate

Re-runs the define-time reachability pre-flight for every member connection in payload.connections, stores the fresh validation report on the estate, and updates status to validated when all members are reachable or partial when any is not. Each member connection is checked as the authenticated human caller. A personal connection is valid when that caller owns and can read it; the validation request does not fall back to a bot identity. Successful responses include data with ok (all members reachable), connections (per-member reachability results), and validated_at.

POST /api/estate/estates/{estate_id}/preflight

Submit-and-return: runs three business-readable readiness checks — agent ACCESS (deterministic reachability for every member connection), ETL understanding (agentic, read-only — the agent locates and reads registered code_assets and returns a run plan; no writes; estates with no code assets fail immediately with no agent turn), and CLONABILITY (lightweight provision-then-teardown for Postgres members). A source-only estate omits ETL entirely and reports it under data.not_applicable, so an estate with no code assets can be ready. The response returns immediately with run_id, top-level status: "running", and data. The ETL check commissions a bounded agent turn on a visible, thread-linked thread; if that turn reaches estate_char.preflight.etl_timeout_seconds (default 600 seconds), the ETL light reports a timeout and names that setting instead of judging a partial reply as malformed. Follow GET /api/estate/runs/{run_id}, GET /api/estate/estates/{estate_id}/preflight/latest, or /api/ws/estate/live?run_id=.

GET /api/estate/estates/{estate_id}/preflight/latest

Read-only: returns the latest preflight readiness view without minting a run. Successful responses include data with ready (all checks green), ordered per-check lights (pending / running / passed / failed, each with a business diagnosis and optional agent thread link), not_applicable (for example, the ETL check and its source-only basis), run_id, and run_status. When the estate has never been preflighted, data is null (not HTTP 404).

POST /api/estate/estates/{estate_id}/code-assets/clone

Clones git assets and persists their resolved clone slots; for Databricks asset shapes, revision capture, identity resolution, and failure behavior, see Estate registration and code assets. Successful responses include data with cloned / captured / skipped / failed counts, updated code_assets, and the updated estate.

POST /api/estate/estates/{estate_id}/code-assets/lineage

Derives dbt transform lineage from the estate’s cloned code assets (manifest-free parser) and reconciles parsed models against the estate’s latest scanned snapshot. Best-effort per asset; fills each asset’s lineage slot. Successful responses include data with derived / models / matched counts, updated code_assets, and the updated estate.

POST /api/estate/estates/{estate_id}/artifact-reproduction

This route implements the reproduce_artifact tool. It executes the declared artifact and writes to the named utility estate; the artifact must already be present in the source estate’s derived lineage. The response is wrapped in data; see the reproduce_artifact tool contract for verdict semantics and optional evidence fields.

GET /api/estate/target-candidates

Each row includes estate_id, name, dialect, database, kind, scanned, last_scan_at, object_count, scan_count, compared_before, and last_verdict (when applicable). When a finished scan exists, object_count is resolved from the latest snapshot’s identity fields; the endpoint does not load the full characterization document just to render this count. A last_verdict for the latest forward comparison can also expose its measured unlicensed, published published_unlicensed, and withheld_unlicensed / withheld_indeterminate counts; the withheld fields are null when the minting run withheld nothing or its withholding is unavailable.

POST /api/estate/estates/{estate_id}/scan

Submit-and-return: the response returns immediately with run_id, scan_id, estate_id, top-level status: "running", and data. Every scan is a new run and timeline event — never a replay of a finished recording. On source scans, the static fault-drill coverage meter chains on the same run only when estate_char.fault_drill.run_on_scan_default is true (schema default false); otherwise use POST /api/estate/estates/{estate_id}/fault-drill to run it on demand. Follow progress live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}.

POST /api/estate/estates/{estate_id}/fault-drill

Submit-and-return: runs only the static fault-drill coverage meter against the estate’s most recent snapshot + compiled contract — never a re-scan and never the adversarial hardening chain. The drill scores the contract’s catch rate against a synthetic copy of the stored characterization, so it works for any dialect (unlike POST .../harden or POST .../spec-drill, whose live cloners are postgres-only today). The response returns immediately with run_id, estate_id, top-level status: "running", and data. Follow the drill phase live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}. When the estate has no finished scan or no compiled contract yet, the run still finishes cleanly with fault_drill_skipped in the summary (not an error). Drills per run are capped by estate_char.fault_drill.max_drills (schema default 250; set 0 for no cap).

POST /api/estate/estates/{estate_id}/harden

Submit-and-return: runs only the source-hardening chain (fault drill + adversarial self-drill + harden/recompile + learnings) against the estate’s most recent snapshot + contract — never a re-scan. The response returns immediately with run_id, estate_id, top-level status: "running", and data. Follow the drill phase live over /api/ws/estate/live?run_id=<run_id>.

GET /api/estate/bot-git-repos

No request body or query parameters. Successful responses include data: a list of local Genesis git repos attachable as estate code assets, each with repo_id, path, branch, and remote_url. Only git-mode repos the operator may view (via the platform’s RBAC-aware git list_repos action) are returned.

POST /api/estate/estates/{estate_id}/spec-drill

Submit-and-return: seals a fresh spec, has a separate agent migrate a throwaway clone, then grades the Prover’s blind diff against the sealed spec end to end. The response returns immediately with run_id, estate_id, top-level status: "running", and data; the graded run-view is persisted into the run summary when the drill finishes. For a Databricks drill whose teardown keeps its lease, the terminal run summary also includes drill_lease_kept with the operator’s DROP SCHEMA instruction. Databricks provisioning runs roughly 140 warehouse statements (70 per catalog) plus the lease; teardown runs about six statements. Follow live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}.

POST /api/estate/migration-envelope/grade

Submit-and-return: seals an empty, platform-clause-only spec (“move source_engine → target_engine, change nothing”) and grades the pair’s persisted comparison against it. The response returns immediately with run_id, status, sealed_spec (spec_hash, sealed_at, spec_name, source_engine, target_engine, n_items), and data. Follow live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}. Idempotent when diff_hash is set: legacy callers that omit transport identity reuse any existing materialized envelope run for the named comparison and receive its run_id with status: "finished" and data.reused: true. Identity-aware callers reuse only an existing viable run with the same snapshot pair, engines, authored envelope fields, estate scope, and bot-plus-authenticated-human identity; a different owner or binding claims a separate run. No new grading task is spawned for a reused run.

POST /api/estate/spec-conformance/compile

Compiles free prose into structured, editable spec items by driving a nested claude CLI subprocess (not the resolved default LLM via litellm.acompletion) over the closed change-kind vocabulary (add_column, drop_column, rename_column, type_change, transform_values, add_table, drop_table, add_derived_mart, modify_etl_derivation, drop_constraint). Out-of-vocabulary kinds are dropped and reported in notes, never smuggled into the editor. The production spec-linter runs over the result. Nothing is sealed or graded — the items feed the unchanged seal+grade flow after human correction. Successful responses include items (same shape as the manual spec editor: spec_id, locus, change_kind, change_expectation, description, expected_effect, propagates_to, new_locus, expected_type, and, when declared, expected_type_family, expected_scale, and type_license) and notes (advisory findings with item_index, kind, and message). change_expectation is must_differ when the target is supposed to change at the locus, or must_match when it is supposed to retain the locus unchanged; the structural tier refuses to score an item that omits it rather than guessing. expected_type_family accepts numeric, text, date, timestamp, temporal, boolean, or binary; expected_scale is a non-negative integer scale for numeric declarations. A type declaration’s type_license is "ticket" when the ticket states or entails the type, or "inferred" when the author selected it by convention. This tag controls mismatch severity: ticket-licensed mismatches produce a repair directive, while author-inferred mismatches produce an informational note. Each note kind is one of ambiguity (a lint finding — a malformed item that would be unadjudicable), assumption (a choice the compiler made from under-specified prose), or unmapped_text (prose the compiler could not turn into a structured change). item_index is null for whole-document notes. On compile failure the response is a clean error with a distinct error_type and a short human error message — never a partial spec, never raw provider or agent text: Failure bodies have the shape { "success": false, "error": "<human message>", "error_type": "<type>" } (omitted for 422).

POST /api/estate/spec-conformance/grade

Author, seal, and grade a change-item spec against the source and target snapshots. The endpoint returns immediately with a run_id and status: "running"; poll GET /api/estate/runs/{run_id} or follow /api/ws/estate/live?run_id=<run_id> for the finished run view. The grade reads its snapshot bindings as the authenticated human caller, not as a bot identity. A personal connection therefore works when that caller owns and can read it; the request does not substitute a bot identity for the authenticated owner. Each item_verdicts entry contains item_id, locus, verdict (pass, fail, or unverified), licensed, obligation_ids, and obligations. Each nested obligation contains obligation_id, the same three-way verdict, licensed, title, optional kind, and an optional election record. The structural attribution still determines where a result appears; the value-obligation verdict determines its status. An obligation marked fail with licensed: true does not become a finding, and unverified items are not treated as successful re-verification. Successful responses include run_id, status, sealed_spec, and data. Graded comparison results identify the comparison engine in comparison_engine_version; it is the recorded version for the comparison, or null when the comparison predates engine recording. When an item declares expected_type_family or expected_scale, grading compares the target column’s type family (and numeric scale when declared) rather than its backend-specific type spelling. Ticket-licensed mismatches fail the structural item and produce a repair directive; author-inferred mismatches remain informational notes. Items without these fields retain existence-only grading. The completed run view can include obligation_verdicts with source, coverage, and normalized items, plus the type_family block when the family tier graded one or more declarations; matched spec rows can also expose verdict, verdict_licensed, verdict_obligation_ids, and verdict_obligations. The verdict also reports items_matched_in_scope, the requested changes the oracle matched by place and shape, and retains confirmed_changes as a deprecated count that also includes clauses confirmed by checks. After obligation verdicts are applied, items_passing counts confirmed rows with a pass verdict, including confirmed removals and items whose only failing check is an accepted difference. It remains null until checks are applied and on a structural-only grade. When the summary has an item list, conformance.items_unconfirmed counts requested changes whose item verdict is fail or unverified; a shape=card run row retains this count under conformance when it drops the items list.

POST /api/estate/spec-conformance/grade-compiled

title (body, string or null, maximum length 200) is the grade’s business title — for example Foundry aviation: raw → ontology rebuild (spec-mediated) — stamped on the run’s provenance.title and shown as the comparison’s headline in the dashboard, above its technical source → target label. Omitting it, or sending a blank one, is deprecated: the request is still admitted, and the response carries Deprecation: true and a Warning header saying the title will be required. The agent tools prove, compare and grade already refuse an untitled call. Grade a registered, seal-verified compiled artifact against a live estate and persist the result. The endpoint runs the artifact’s obligations against the two registered connections and its structural items against the mediated diff for the source and target snapshots. Unlike POST /api/estate/verifications with compiled_spec_hash, this endpoint persists one spec_conformance run and returns the durable #N identifiers of findings it minted. Register an external artifact first with POST /api/estate/spec-conformance/compiled; the artifact is never re-compiled here. This is a synchronous, warehouse-bound operation: one comparison runs per obligation, with a query on each side for keyed obligations and one count for an invariant, followed by grain probes as needed and structural grading. A real artifact may take minutes, and the work is dispatched off the API event loop. The route requires ratify_source_contract and records the authenticated caller as the run’s operator; it does not accept an operator identity in the request body. Before the service can inspect or reuse a prior result, both submitted connections must be registered and authorized for the exact bot-and-human transport identity. A revoked caller receives the same disclosure-safe 400 as an unknown or unregistered connection, without the prior run being named. When diff_hash names a comparison from a retired diff engine, structural grading recomputes it under the current engine on the same stored snapshots and checks the snapshots’ measurement-library versions. The run provenance identifies both comparisons under comparison_recomputed_under_engine; a snapshot that another measurement library took, or a comparison the recompute cannot reproduce, is refused with 409. The Prover overview describes the recompute and refusal conditions. A type_mapping that can carry fractional values into a scale-zero numeric type is conditional: only a source column measured with zero fractional values is licensed. For each such candidate column, grading reads the live source (not the comparison’s source snapshot), with one full counting scan per source relation containing candidates. A failed or unavailable measurement does not license the mapping. When measured, run provenance includes whole_numbers_read with read_at, columns, compared_source_snapshot_id, and basis so the live-source reading is distinguishable from the comparison snapshot. For how derived_checked affects structural findings, see the [Prover mapping guide] (/docs/Genesis-Prover/Overview#declared-target-mappings-and-data-vault-shapes). When compiled_spec_hashes is supplied, the service grades the named specs as one set for a table specified in parts. The set must contain at least two distinct hashes; a one-member set is rejected, and the schema rejects an empty list or a request that supplies both forms. The set has its own spec_hash, derived from its member hashes in any order, while spec_hashes lists the sorted members in the response, submit handle, and reused result. Each member is loaded and seal-verified; the set itself has no seal. Set attribution is ownership-based: a column belongs to every member whose structural item covers it or whose declared target_model mapping carries it. A column covered by more than one member is listed in spec_set.overlaps. A column covered by no member is the only column-level drift in a set grade; its reason says not covered by any spec in this set and it remains for adjudication rather than receiving the single-spec implementer_error lean. Object-level differences retain their ordinary grading behavior. Every member’s obligations run, and each obligation is attributed only to its own member’s items. An obligation’s member is the spec that sealed it, unless it names an owning_relation — the one table it is about, which its SQL reads — that exactly one member owns (a scope entry expecting same, changed or new of it, a structural item on it, or a declared mapping into it). Then it is that member’s. A check spanning two tables, sealed by the spec that holds the other table only as context, uses this to belong to the spec that owns it; with no owner or several, it stays with the spec that sealed it. If members claim different values for the same column’s change kind, new locus, expected type family or scale, or change expectation, the result records a spec_ambiguity finding against the conflicting specs and leaves neither claim graded. Conflicting scope expectations leave the object out of the merged scope. These conflicts appear in spec_set.conflicts; SQL obligations are not compared for conflicts. The run view also includes spec_set.item_owners, declared_columns, and the member hashes used for each overlap or conflict. The warehouse work is the sum of running each member’s obligations. The request identity includes the sealed spec, both connections, both snapshots, resolved engines, catalogs as resolved, invariant_side, diff_hash, estate_id, estate_coordinate, round_n, and tiers. On Databricks or Snowflake, an omitted catalog resolves from its side’s named snapshot database, so omitting it and naming that same database are one request; naming a different catalog is a different request. The snapshot pair and diff_hash are hashed as omitted unless tiers includes structural, because only that tier reads them: the same derivational-only grade submitted once bare and once carrying a comparison it never uses is one request, not two. That is why supplying a diff_hash without the structural tier is refused rather than dropped — a grade that ignored it would name no comparison, which the adjudicator refuses permanently, and a re-submission adding the diff_hash alone would hash to the same identity and return that same run as reused. The rule is decided ONCE, at the service admission boundary, for every caller; this route’s 400 is a mapping of that verdict, and a caller invoking the service directly receives the raised error with no status code. The refusal text is the same at both. Adding the tier does mint a new run, because tiers is unconditionally part of the identity. When supplied, estate_coordinate likewise distinguishes a repeat measured against a changed estate from the earlier run. run_label and task_id are excluded, but the exact bot-and-human transport identity is retained in the request key. An identical authorized request from that same identity resolves to the same run; a different credential scope gets a distinct request identity and cannot receive the earlier caller’s run. A finished run is returned with reused: true; a run still in progress is not returned as a verdict and instead produces 409 with the run id to follow. Errored runs do not satisfy a repeat, so the next identical request can grade the round again. The claim is atomic within one backend process; stale abandoned runs are released by the periodic stale-run reconcile. For catalog-qualified deliverables, target_catalog is used on both sides of value obligation attribution: a target reference such as target_catalog.schema.table can match the structural locus for that object, and a column-grain reference can include the column as the fourth segment. The completed run normalizes a matching locus to schema.table or schema.table.column; a different catalog is not attributed to the graded item. Catalog binding is decided once during grade admission. For an engine that cannot bind a session catalog, naming the database recorded by that side’s snapshot identifies the connection and proceeds without a catalog; naming any other database, or naming one when the snapshot records none, returns 400 with no obligations graded. Omit the field when the connection’s own database should be used. This same rule applies to the catalog fields on POST /api/estate/verifications when compiled_spec_hash is supplied. The completed grade summary makes measurement coverage explicit. obligation_verdicts contains items for obligations claimed by structural items and unattributed for every obligation that claims none. Failed or unverified obligations in either holder are included in the persisted finding population sent to the Adjudicator; reading items alone can omit failures. Each obligation verdict also carries requirement_source, either the declared {kind, citation} provenance or null when the seal has no readable declaration. This is display context only and does not affect grading. It also contains unverified, a list of every obligation that did not reach PASS or FAIL, with its obligation_id, title, relation, and error. relation is the declared locus for an invariant and null for comparison obligations, so an errored invariant still names the relation it governs. An errored entry can also include errored_sql: source_sql_sha256 and source_sql_chars, and/or target_sql_sha256 and target_sql_chars for the statements declared by the seal. These pointers identify sealed SQL without including its text in the run record; use the run’s sealed-spec hash to read the artifact. If source_read records a landed-copy substitution, errored_sql still hashes the sealed statement, while source_read.statement describes the rewrite. Each entry in data.obligations also carries the same relation and its own error when the instrument failed. When no declared obligation reached a verdict, the conformance block sets no_drift, drift_count, and verified to null and adds a drift_basis explaining that no measurement was made. Partial grades measure the decided obligations and use drift_basis to name the errored remainder. The coverage block also reports nothing_to_check for obligations whose checks measured an empty population and items_nothing_to_check for structural items whose attached obligations all had that outcome. These counts are separate from decided, errored, and unmeasured obligations: an empty check is neither a pass nor a check that could not run. For the obligation_verdicts.clauses_not_checked interpretation against the seal’s clause_gate, including the case where a commissioned document has no clauses, see how the Prover grade reports unchecked clauses. The run envelope also exposes the same obligation totals as summary.obligation_counts: obligation_count, passed, failed, errored, unmeasured, and nothing_to_check, plus failing_obligation_ids (all failing ids, not capped at ten), obligations_at, and detail_path (the run-detail path). The count is absent when the run recorded no obligation coverage. summary.items remains the structural tier’s spec-item list and can be empty on a derivational-only grade; use summary.obligation_counts to tell what obligations were measured. The obligations_at paths point to records under summary.obligation_verdicts.items[].obligations and summary.obligation_verdicts.unattributed. For a multi-tier grade, wall_seconds includes the earlier derivational work as well as the structural tier, so it reports the run’s elapsed time rather than only the final tier’s duration. When structural items failed, summary.obligation_counts.failed_items groups the failing spec-item count by the checks that failed them. It has count and by_check; each entry names an obligation_id and the number of items it failed. An item failed by more than one check is counted under each, so per-check counts can sum to more than count. The grouped counts also appear in the grade headline. When more than six checks failed items, the headline names the five largest groups and points to verdict.failed_items for the remaining groups. An absent failed_items means the summary did not record this grouping. The structural conformance block’s derived_checked, derived_unchecked, and derived_unchecked_reasons are null when the comparison reported no derived target whose whole-table digest it skipped; derived_basis explains that no split applied. When there is a split, the lists and reason map are populated and derived_basis is null. A run view rebuilt from a stored summary that recorded no classification also returns null for these fields, but derived_basis says the split was not recorded. For a committed fork, summary.readings_not_taken lists the rival obligation ids whose FAILED readings were not taken by the build. The rival is marked licensed and counted with the passes only when exactly one committed lean identifies the fork’s taken reading and every fork member is decided; the election record names reading_taken, taken_by, reading_not_taken, taken_verdict, and the committed_lean_held basis. When the leaned reading itself fails (taken_verdict: "fail"), it stays the fork’s one failure at its own row count, and the failing rivals are still reported as readings not taken rather than as failures, since a rival differs from the taken reading by design on every build. An uncommitted fork, an undecided member, conflicting committed leans, or a failed leaned reading beside a passing rival (the build took the rival) leaves the readings graded as-is. For a by-reference grade, spec_hash is the submitted compiled_spec_hash and projected_spec_hash is the separate, unregistered seal of the structural projection created for that run. Direct-envelope and derivational-only grades report projected_spec_hash: null; use spec_hash_basis and projected_spec_hash_basis to interpret those values. Runs created before these fields were introduced are not back-filled. For the series state machine, open-demotion behavior, attribution rules, and unmeasured cases, see How agents drive the Prover. This endpoint persists the resulting obligation_pass_to_fail_ids, obligation_regressions, obligation_regressions_basis, and obligation_regressions_unmeasured fields in data.summary; a tier that cannot be measured is omitted from certification rather than reported as a clean empty list. The findings tier carries its own findings_regressions_basis because this product path does not have per-round finding verdict vectors for the same comparison. The same summary carries structural_observed_at, structural_observed_through, obligations_read_at, obligations_read_through, and vintage_basis. The first pair is the structural snapshot interval; the second is the sequential live-read interval. Fields are null when their tier did not contribute a reading. The closed vintage_basis values are single_tier, same_vintage, blended, and vintage_unknown; see how a run reports observation vintage for their interpretation and the zero-obligation behavior. A blended grade also carries vintage_between_reads, {status, source, target}, whose status is observed_changed when write history shows a newer data write, confirmed_unchanged when every table’s history confirms no write, or not_confirmable when an estate has no history Prover reads or its history could not be reread. For what not_confirmable does and does not establish, and when the caveat says nothing was seen to change, see how a run reports observation vintage. The grade’s vintage_caveat names changed tables or gives the reason confirmation was unavailable, and is null when the grade is confirmed unchanged. vintage_between_reads is null for every other basis. GET /api/estate/spec-conformance/run-for-diff/{diff_hash} returns the grade’s vintage_caveat and vintage_between_reads beside data. Successful responses include: An unclaimed failed obligation check normally creates a durable finding at its declared relation, even when no structural item claims it. If it has recorded table reads and every read is qualified to a database other than the graded catalog, it remains a review blocker but does not mint a finding; that is an authoring fault, not a target-build defect. For a compiled grade, data.summary.spec_seal_state is full or partial. A partial grade also carries data.summary.open_clause_count and data.summary.open_clauses; each entry has clause, text, reason, remedy (actor, kind, statement, options, measured_candidate), evidence (measurements with sql and result), and depends_on. data.summary.verdict.open_clauses is the count. When the grade would otherwise be verified or verified_with_indeterminate, the partial seal sets its status to verified_with_indeterminate and verified to false. Its headline uses Partially specified · N clauses open (singular clause at 1), not Verified wording. A grade submitted with check_ids has data.summary.spec_seal_state: "subset", plus data.summary.graded_check_ids and data.summary.spec_check_count. Its headline identifies the selection as Partial grade · N of M checks; passing checks remain amber at best, never Verified. It does not replace the comparison’s or spec’s latest grade and cannot be approved. The Prover guide describes how to use the result. When a stored grade has no spec_seal_state, the reader uses its provenance.grade_engine_version when that value is dotted unsigned ASCII digits: a version older than the partial-seal reader is read as full, regardless of grade time. For a missing or unreadable version, a known grade time before 2026-10-14T05:00:00+00:00 is read as full; without a grade time, a missing version is full and an unreadable version is unknown. Other missing-state grades are unknown, not Verified. For a set grade that includes the structural tier, data.summary.spec_set_reading is the card-ready reading of the set. It contains headline, members, overlaps, conflicts, not_covered_by_any_spec, and not_covered_count. members identifies each spec_hash as Spec 1, Spec 2, and so on, and includes that member’s seal when the run provenance has one. Each overlap gives its locus, the covering spec_hashes, and a reading such as <locus> is covered by 2 specs. Each conflict includes both claims in first and second, the locus and field, and a reading that says neither claim is graded until the specs agree. Each uncovered difference gives its locus, comparison, and a neutral reading that it is not covered by any spec in the set; not_covered_count is the exact count when the stored summary provides it. This block is absent for a single-spec grade and for a derivational-only set grade. For the non-persisting, submit-and-return alternative, see POST /api/estate/verifications with compiled_spec_hash.

POST /api/estate/adjudication/runs/{run_id}/adjudicate

Send one readable Prover subject to the independent Adjudicator for attribution. The path accepts a spec_conformance, spec_drill, or system_diff run, and a prove run handle resolves to the grade when one exists or to its comparison otherwise. A no-spec proof therefore reaches judgment through its comparison’s own elements rather than through an empty grading envelope. The server assembles the subject from persisted findings and comparison materials; callers do not supply or rewrite the findings. The route requires ratify_source_contract and records the authenticated caller as the invoker. The response returns immediately for a fresh proceeding with status: "running"; poll the returned adjudication_id using get_adjudication_run for the verdicts. A repeat with the same (run_id, round_n) normally returns the existing proceeding with reused: true and its recorded verdicts, without commissioning another bot turn. This deduplication is per backend process, not an exactly-once guarantee. A fresh proceeding needs an LLM provider — the seat is a bot turn — and without one the route answers 503 with details: "llm_provider_not_configured", naming the cause, the remedy (Settings > AI Providers) and what still works; a repeat over a round that already has a proceeding is a read and answers 200 regardless. A provider that is configured but does not answer a readiness check (an expired login, an unreachable endpoint, a spent quota) is refused the same way with details: "llm_provider_unreachable", plus cause (login_expired, unreachable, rate_limited or provider_error) and operator_detail, the provider’s own error for an operator. Nothing is started. If an earlier stalled Adjudicator turn could not be stopped, a new proceeding over the same comparison is refused with 400 while that turn is still in the runtime. Each replacement request and reconcile sweep asks the runtime to stop it again; a replacement can open once a stop request is accepted or the turn leaves the runtime. For a grading subject, adjudication reads the run’s persisted durable finding population, including open rows from both the obligation and type_family ledgers. For a comparison subject, it reads the comparison’s classified elements, retaining unlicensed, indeterminate, and errored members and counting match and licensed elements in its composition. When a subject has no findings, no proceeding is opened and the response explains why in no_findings_basis; a no_findings status alone is not evidence that the run was clean. clean_run means the recorded Human Review blocker population was complete and empty, review_blockers_only means blockers remain but none is a durable finding for this seat, blockers_unmeasured means the blocker count could not be read or the population was incomplete without finding a blocker, and clean_comparison means the comparison census contains only matches and licensed differences. For the blocker worklist and its measurements, use GET /api/estate/comparisons/{diff_hash}/review/population. A verdict may carry the instrument_defect outcome; see False alarm (Prover) for its user-facing label and behavior. Successful responses include:

POST /api/estate/spec-conformance/author-seat

source_estate_basis has the shape { "connection_id": string, "reason": string }; both fields are non-empty and required when the object is present. On Snowflake, an unbindable catalog name is refused before the authoring run is created; the author_spec contract documents the accepted identifier forms. source_catalog and target_catalog can place both estates on one connection as separate catalogs. In that placement, source_catalog binds seat statements except the registration check for an invariant declaring invariant_side: "target"; target_catalog binds that check along with target-side reads and preparation. The seat’s ad-hoc _query_database reads use the target catalog only when every schema named by the query exists there and at least one is absent from the source catalog; otherwise they use the source catalog. See the author_spec catalog-binding guidance for the full behavior. A successful target-catalog check is recorded as resolution_target_catalog, not as source observation evidence. When target_connection_id names another connection, target_catalog belongs to that connection and is used only for target-engine preparation. Omitted catalogs use the applicable connection’s own catalog. A named catalog on an engine that cannot bind session catalogs returns 400 and no run is created. target_connection_id must name exactly one registered connection that the caller may use. An unregistered or ambiguous reference returns 400, a connection the caller may not use returns 403, and an unreadable connection store returns 503; none creates a run. The connection is kept on the run row, and the seal reads it from there. The seat run is asynchronous by default: the response returns a handle with data containing run_id, thread_id, status: "authoring", mode, interpretation_policy, and authoring_tooling, plus the common run envelope. The hidden thread is retained as the run’s audit transcript. The seat’s status is derived from that thread and the compiled-spec store. The seat’s data.status uses one word per run: authoring while the stored run is queued or running, sealed for stored succeeded, and refused, failed, or cancelled for those stored terminal statuses. The common envelope beside it keeps the stored spelling and exposes it as summary.raw_status when that spelling differs from the normalized run vocabulary. sealed is returned only when spec_hash names a registered compiled artifact. A sealed response includes compiled_spec_url for GET /api/estate/spec-conformance/compiled/{spec_hash}; a refused response indicates the seat returned spec_hash=NONE; malformed or unregistered seals are failed. When a conversation resubmission omits target_model and the pre-check carries a mapping registered by an earlier round, the submit handle includes data.target_model_carried_from, the run id of a round whose seat registered that mapping. The author_spec contract documents which rounds are eligible when a commission’s estate_id changes and when an omitted mapping is refused before a run. resubmission_of explicitly names an earlier run and replaces the conversation lookup; it allows an HTTP caller, which has no authoring conversation, to carry a mapping. The named run must be a spec-author seat run even when target_model or repair_of is supplied. If target_model is omitted, a mapping is carried only when the named run’s seat registered it (or it carried a mapping registered by an earlier seat); a named run with no mapping carries none. The seat checks the carried mapping against this request’s source; the named run’s spec_name, connection, and estate do not have to match. A supplied target_model is used as sent, and with repair_of the repaired seal’s mapping is used; in either case the named run is checked for seat type only. An unknown run and one the caller may not see both return 404 Unknown run; a non-seat run or an unregistered proposed mapping returns 400 naming resubmission_of. These refusals create no run. At finalization, the seat compares every relation-observation receipt’s connection with the requested connection_id. A receipt from another connection is refused as re_anchored unless source_estate_basis explicitly names that substitute and explains why it is accepted as the source. An accepted substitution is recorded in the sealed artifact and finalize result as re_anchored_from; ordinary seals expose the declared source and the reason no substitution was recorded. See relation observation evidence for the receipt and seal fields. When assets is supplied, the response’s data.intake reports the caller’s inventory before the seat derives any obligation. Every named asset must be labelled canonical or hypothetical; the seat never supplies or corrects a label. A bundle containing a hypothetical asset runs a findings pass and does not seal (data.intake.phase is findings). Submit the resolved bundle again with all assets canonical to author and seal it. Intake and sealed assets also carry server-stamped asserted_by and label; see the author_spec tool contract for the attribution semantics. Document-carrying intake and sealed assets also carry the document pin: source, chars, content_sha256, and the storage metadata needed to identify the bytes actually read. The document itself is not in the summary or brief; the seat reads it with read_spec_asset. Omitting assets, or sending an empty list, is the no-separate-assets case and authors normally. Before creating the run, the route refuses a ticket and its document assets when their combined numbered items and table rows exceed the seat’s registration capacity: 100 clauses per accounting call times estate_char.spec_author.max_registration_calls (default 200). This returns 422 and no run handle. If a pinned document later cannot be read during finalization, the run ends refused with the recorded rule asset_documents_unreadable; see the author_spec tool contract for the refusal details. When clarification_ids is supplied, each record must exist and belong to estate_id; otherwise the request is refused with no authoring run. The seat records the ids and their stored provenance in the sealed specification; see the clarification tool contract for the recording rules. On a repair, inherited ruling ids are listed separately in the run summary as clarifications_carried_from_lineage; the caller’s clarification_ids remain the explicit submit-time list. The summary also records earlier_owner_rule_offers and exact repeated offers withheld from the owner in owner_rule_offers_already_made. The seat’s repair brief behavior and when earlier questions or checks may be raised again are described in the author_spec contract. target_type_dispositions is validated at submit: a disposition for an undeclared, duplicate, non-TIME, or time-zone-bearing column returns 422 before a run starts. For a Databricks or unidentified target, the asynchronous seal refuses with target_time_encoding_unratified if a declared TIME column lacks a matching disposition ratified by a signed-in person for the same estate and exact storage choice. The current accepted disposition is STRING encoded as HH:mm:ss; choosing native TIME as the disposition’s physical type is not accepted yet, and a time-zone offset cannot be represented by this encoding. Each disposition that reaches ratification validation causes two clarification-store reads at seal. See the author_spec contract for the statement format and caller workflow. With wait_seconds, the route returns 200 if the seat terminalizes during the wait. If the wait expires first, it returns 202 with the same handle, status: "authoring", and wait.expired: true; the seat is not cancelled and the caller should poll on. The generic GET /api/estate/runs/{run_id} route is the common poll surface, while the seat-specific status route exposes the seat’s counts, findings, and open questions. If the seat’s thread and completion make no progress for the configured estate_char.spec_author.no_progress_seconds bound, the driver ends the run with terminal status: "error" and summary.error_type: "run_stalled". The response’s next action says to start the run again after checking work already committed. The driver reads activity from the runtime that dispatched the turn, not only from the completion stream; an unreadable runtime view does not declare a stall. Before writing the terminal row, it asks that runtime’s hard-stop to stop the turn and retries a refused request according to the configured stop policy. The summary says whether the stop was taken or the requests ran out; anything written afterward is recorded as a late completion beside the terminal row. If all stop requests are refused, the process keeps asking on reconcile sweeps and replacement submissions; a new run for the same operator and connection is refused with 409 until a stop is accepted or the turn leaves the runtime. A poll that cannot read the thread counters or the owning runtime does not declare a stall. Before creating the run, the route checks that the configured model provider answers. If no provider is configured, it returns 503 with details: "llm_provider_not_configured". If a configured provider does not answer, it returns 503 with details: "llm_provider_unreachable", cause (login_expired, unreachable, rate_limited, or provider_error), and operator_detail; no run or thread is created. The readiness probe is shared across model-backed seats according to llm.provider_preflight_ttl_seconds.

GET /api/estate/spec-conformance/compiled/{spec_hash}/substrate

Read only the SUBSTRATE section of a seal-verified compiled spec. This is the customer’s stated requirement for how the change must be built; it states no obligation and is not graded. The response’s data contains spec_hash, declared, and clauses. declared: false means the ticket stated no substrate, so clauses is empty for that reason.

POST /api/estate/spec-conformance/compiled/{spec_hash}/self-certification

Record the building agent’s self-certification against the sealed substrate. This is a statement, never a proof: nothing measures it and no Prover verdict reads it. The response is labelled kind: "self_certification" and is_proof: false. When the spec declares no substrate, the statement is recorded as the builder’s substrate declaration instead: the clauses are the builder’s own (what it built on and where it departed), and the response is labelled kind: "substrate_declaration" and anchored: false — it is not certified against anything the spec stated, and the spec does not gain a substrate. A statement against a declared substrate is labelled kind: "self_certification" and anchored: true. The request is refused with 400 when a clause is not present in a declared sealed section, when a declaration names no clause, or when a clause is submitted twice. An identical statement is idempotent: the response returns the same certification_id and created: false on a retry. A new statement is appended rather than replacing an earlier one.

GET /api/estate/spec-conformance/compiled/{spec_hash}/self-certification

Return every self-certification for the hash, newest first. The response is labelled as a self-certification and never as a verdict. An empty certifications list means nobody has certified against the spec; it is not a finding or a non-compliance result. Each row carries kind and anchored as above, so a builder’s declaration on a spec with no substrate never reads as a certification against it.

GET /api/estate/spec-conformance/author-seat/{run_id}

The response’s data contains run_id, thread_id, status, mode, and the authored counts (structural, obligations, settlements, and finalize_rounds) when available. On a terminal run, finalize_rounds remains the seat’s own count from its RESULT line, while finalize_round_history carries the finalize attempts recorded on the run row, including each attempt’s status, findings, and refusal rules. result_line_disagreed names finalize_rounds when the seat’s count differs from the number of recorded attempts, even when no artifact was sealed; the count remains the seat’s self-report, so the marker does not decide which side is wrong. The terminal summary also retains authoring_effort, with the run’s authoring cost and counts by kind. It also includes findings and open_questions; each open-question entry carries its number, text, status (open, answered, or proceeded), owner_answered, and the current resolution label and metadata. summary.open_questions contains questions put to the owner; migration-default questions that were held back are retained separately as summary.migration_default_questions, an array of {question, property} objects. The field is omitted when empty. See [resolve_open_question] (/docs/Genbot-Toolkit/Additional-Tool-Groups#resolve_open_question) for how that field distinguishes an open or agent-closed question from an agent-recorded answer. A full or partial sealed result includes spec_hash and compiled_spec_url. When a sealed seat still has an open question, the common run envelope’s next names resolve_open_question with the next question number instead of handing off directly to prove; after all questions are resolved, next hands off to prove. Statuses are authoring, sealed, sealed_partial, refused, failed, or error. For status: "error" with summary.error_type: "run_stalled", use the next action to start a new run; the original turn may still write a late completion because it was not interrupted. The common envelope’s summary.seat_settings_defaulted lists authoring settings left unset; summary.seat_settings_inherited records inherited settings and catalogs. See the author_spec contract for submit-time tooling inheritance and the load_prior_spec reference for catalog inheritance. The terminal summary also retains Registrar activity under registrar_phases and registrar_withdrawn_obligations, including ruled withdrawals, so polling the run preserves checkpoints and withdrawals recorded during authoring. Its registrar_phases.checkpoints list is newest first in the run summary, marked by registrar_phases.checkpoint_order: "newest_first"; the seal’s checkpoint record remains oldest first. This ordering also applies when a run ends without a seal. If an oversized response is shortened by dropping trailing list elements, this order keeps the final review rather than only the earliest draft checkpoints. The terminal summary also includes sealed_obligations, a mapping from each sealed obligation_id to its kind. It is placed first in the summary so a byte-offset fallback retains it unless the mapping itself does not fit. When the sealed artifact has a clause-accounting ledger, clause_dispositions counts its entries by accounted_by value: obligation, accepted_difference, not_applicable, and open. The three closed-disposition counts are present, including zeroes; open is present when any clause is open. clause_dispositions is omitted when the seal has no ledger. Each sealed run also reports spec_seal_state (full or partial) and open_clause_count, measured from the artifact (0 for a full seal, null when a partial seal’s coverage block cannot be read). For a partial seal, open_clause_remedies lists each open clause’s clause, reason, remedy actor, and remedy statement as remedy; it is null if the block cannot be read. result_line_disagreed names open_clauses when the seat omits the required partial-seal count or reports a count that differs from the artifact. On a repair, the summary can also include clarifications_carried_from_lineage, earlier_owner_rule_offers, and owner_rule_offers_already_made; the first distinguishes inherited rulings from ids supplied by the caller. The run’s provenance.seat_settings_defaulted also records which settings the deployment default decided. Every read of the run — the envelope, the data half, each run-list row and the seat status view — shows a provenance setting the round inherited as the inherited value, and neither seat_settings_defaulted list names a setting once it has been inherited. The questions are resolved through the prover_tools resolve_open_question tool, not this status route. Supply either a clarification recorded with record_clarification or an explicit reason for proceeding without an answer; the resolution is append-only and the latest one for a question is the current state. The same open_questions view is included when reading the compiled spec that the seat sealed.

POST /api/estate/spec-conformance/compiled

* Supply exactly one artifact form: artifact, artifact_text, or file_ref. Supplying none or more than one returns 422. Use this endpoint when an agent or other authoring process sealed the artifact outside the Genesis server. Prefer file_ref when the sealed file is uploaded; otherwise send its exact text as artifact_text. Use the parsed artifact only for content assembled as an object. Do not send a seal: the server derives the seal from the artifact, verifies the claimed spec_hash, validates the artifact shape, and then stores it so GET /api/estate/spec-conformance/compiled/{spec_hash} can serve the seal-verified view. The authenticated caller is recorded as registered_by in provenance, together with the registration timestamp. Successful responses include data with registered: true, the spec_hash, already_registered, advisory findings, and content_sha256 plus content_sha256_basis. When the artifact’s model lists unassessed tables, findings also include unassessed_but_assessed if its scope, structural items, or obligations assess one, or unassessed_without_scope if the spec declares no scope. These findings report contradictions in externally sealed artifacts but do not block registration. For byte forms, content_sha256 is the digest of the bytes supplied and the basis says whether they are the sealed bytes or a different serialization that canonicalizes to the sealed content. For the parsed-object form, it is null with a basis stating that no caller-supplied bytes were measured. Re-posting an already stored hash writes nothing and returns already_registered: true; findings do not block registration. That no-op is on the CONTENT only: because nothing is written, the coverage in such a response is measured against the stored row’s OWN parent and carries a coverage_basis saying so, rather than against the parent_spec_hash the repeat supplied — which the store would not have honoured. coverage and coverage_drop_basis are the gate’s own record and are stored in a schema-owned field of the row, never inside provenance: a caller-supplied value under either name is stripped at the write, on every path. The separation is what makes the legacy answer sound — provenance is a free-form namespace the caller fills, and filled it under the previous contract too, so a key sitting in it says nothing about whether the gate ran. A repeat that declares a different parent_spec_hash than the stored row is refused 400 rather than answered, since re-posting identical content cannot move stored lineage. COVERAGE is the one thing that does block, and only when parent_spec_hash names a spec this deployment holds: the response then carries a coverage block measuring this artifact against that parent (obligations and structural_items each as before/after, dropped_obligation_ids, added_obligation_ids, dropped_structural_ids, per-kind counts, and shrank), and a reseal that asserts LESS is refused 400 unless coverage_drop_basis states why. An absent coverage block means nothing was measured — read coverage_basis for which case — and never that a parent was declared and nothing was dropped. Every response carries a coverage_basis, including a first registration that declared no parent, which reports no_parent_declared and no coverage: an original is a state the answer names, not one a caller infers from a missing key. The other bases are not_measured_at_registration (a parented row admitted before the gate existed), parent_unavailable (measured once, its parent has since left the store), read_not_taken (this request wrote nothing, so the answer describes the row that persisted) and measured_against_unmeasured_parent (measured against a parent whose own lineage never was). Rows admitted before this gate existed have one stated policy that every read path obeys: re-posting such a row is idempotent success carrying coverage_basis: not_measured_at_registration and no coverage, because a measurement taken now is not the registration-time state the gate attests, and it is not refused even when the parent it names has since left the store — reading a record back is the documented way to see it. A NEW reseal whose parent is one of those rows IS measured against the content the store holds, is still refused 400 if it asserts less, and reports coverage_basis: measured_against_unmeasured_parent, since the diff is true about that one link and claims nothing about the chain above it. A row the gate DID measure whose parent has since left the store reads back with coverage_basis: parent_unavailable, which is a different fact from never having been measured. Declaring a parent_spec_hash this deployment does not hold is refused 400, because the coverage it claims cannot be measured — register the parent first, or drop the field. A mismatch names the claimed hash, the supplied-byte hash when available, and the canonical content hash, with the byte form to use instead of retyping the artifact. On Snowflake, registration claims both the content hash and the (parent_spec_hash, round_n) repair-round slot in the write. Concurrent different artifacts cannot both register under the same parent and round; the losing request is refused as a round conflict. Re-posting the same stored content under its existing lineage remains idempotent and returns already_registered: true.

GET /api/estate/spec-conformance/compiled/{spec_hash}

Successful responses include data with the seal-verified artifact view: artifact (obligations and metadata), seal, provenance (including retained instruction_text and, when ticket prose was submitted, instruction_text_submitted_by, canonical spec_files ([{name, markdown}]), spec_files_sha256 integrity hash, target_manifest when present, seat_settings_defaulted when recorded, and the stored clarification and asset provenance), who_decided (the clarification reliance sentence, each clarification with who recorded it, each asset with who asserted its label, and the ticket-prose byline), open_questions (the numbered questions the sealing seat reported, each with its current status, resolution metadata, and display label), and repair lineage (round_n, parent_spec_hash, and related fields). A seal made before clarification provenance was recorded reports clarification provenance unknown. Compiled artifacts include artifact.notes.doctrine_version set to 2026-08-29.grain, identifying the compile doctrine used. Numeric implied-domain misses with a resolvable relation are added as deterministic mandatory invariants for non_negative (min: 0), percentage (min: 0, max: 100), and ratio (min: 0, max: 1); label_set domains remain author-resolved because their allowed values must come from the prose or schema. Axis coverage is checked after cross-family assembly: a cited settlement whose settled_group exactly matches the detected axis id settles that axis even when its resolving sentence does not meet token-overlap coverage, and artifact.notes.ambiguity_axes carries each axis disposition. When the soft fork budget is exceeded, excess ambiguity groups are demoted to plain obligations and retain their underlying checks rather than dropping alternate readings. The open_questions entries use the same shape as the author-seat response above, including owner_answered. offered_not_adopted lists checks the sealing seat offered the owner as stated rules that this sealed artifact does not carry. Each entry has obligation_id, statement, seat_run_id, and the server-owned label explaining that the check is not graded. A check appears once by obligation id; registered obligations in artifact are the adopted checks. This list is visible context only and is not part of grading. The read includes it with the seal by default; the service’s artifact-only path can omit both this list and open_questions.

POST /api/estate/spec-conformance/compiled/{spec_hash}/qualify

Submit-and-return: loads the parent seal-verified artifact, qualifies bare relation names against the named connection using the same normalization as the spec-author finalize path, and registers a new sealed child chained with parent_spec_hash and an incremented round_n. This operation is synchronous because qualification is deterministic; it does not invoke an author, create a run, or create a thread. The successful response returns data with the new spec_hash, parent_spec_hash, qualified (one from/to receipt per changed relation field), author_invoked: false, and the normal registration result. Only relation fields may change. The operation refuses with 400 when nothing needed qualifying, the pass would change any other field or remove an item, the qualified artifact fails the seal gate, the parent is tampered or unsealed, or the connection cannot resolve the names. An unknown parent returns 404; an invalid connection_id returns 422. Use this endpoint for deterministic name qualification instead of a model-backed repair round.

POST /api/estate/spec-conformance/compiled/{spec_hash}/declare-shape

Declare the migration shape for a sealed specification whose structural items do not yet say whether their loci should change or remain identical. This is a synchronous derivation: it does not invoke an author, create a run, or query a warehouse. It returns a new child spec_hash chained to the supplied parent; grade that child instead of the parent. The declaration is written onto every structural item. The response’s data includes the new spec_hash, parent_spec_hash, declared receipt, author_invoked: false, and the normal registration result. A must_match declaration is refused for a drop item, an override is refused when its spec_id is not present, and a specification with no structural tier cannot be re-sealed.

POST /api/estate/spec-conformance/consult-reading

* Supply exactly one prose source: compiled_spec_hash or prose. Supplying neither or both returns 422. When compiled_spec_hash is supplied but the artifact did not retain instruction_text, pass raw prose instead (400). Submit-and-return: the response returns immediately with data containing { "run_id": "...", "status": "running" }. The blind author sees only the resolved prose, the ambiguity-group label, and the candidate reading — never the implementer’s artifact, tests, or verifier content. Poll GET /api/estate/spec-conformance/consult-reading/{run_id} for terminal status. On completion, verdict is licensed (the reading is defensible from the prose and may join the ambiguity group as an election) or rejected (not defensible; malformed model responses are coerced to rejected). The consult identity is the authenticated caller, never a body-supplied bot.

GET /api/estate/spec-conformance/consult-reading/{run_id}

Successful responses include data with the persisted run row:

GET /api/estate/spec-conformance/run-for-diff/{diff_hash}

Deterministic, uncapped lookup of the spec_conformance or spec_drill run whose envelope this comparison should open. The newest specification’s authored grades (target_items > 0) rank ahead of the platform-only migration envelope (target_items == 0); within a tier, the newest grade by parsed started_at wins whether or not it verified (spec_conformance before spec_drill when start times tie). A newer unverified grade therefore replaces an earlier verified grade for display. When the selected grade is not verified but an earlier grade with the same spec_hash verified, its run row includes earlier_verified_grade with that earlier run’s run_id and started_at; the most recent such verified grade is named. The field is absent when the selected grade verified or no earlier verified grade of the same spec exists. A still-running grade is the fallback when no finished viable run exists. Successful responses include data with the full run row (summary, canvas, provenance) when a viable grading run exists, or data: null when the comparison is ungraded — the common case, not an error. Read-only: never mints a run or fires a grade. Use this when the recency-shaped GET /api/estate/runs feed may have dropped an older envelope run for a specific comparison. With include_proceedings=true the response also carries proceedings (addressing columns only — no materials and no verdicts; read one in full through the adjudication routes), proceedings_basis, proceedings_truncated, and proceedings_grading_run_id. proceedings_basis separates three facts an empty list alone cannot: measured (this comparison’s grade WAS listed and held these), no_grade (nothing was listed because no grading run resolves for the comparison at all), and not_requested. They prescribe different next moves. Both halves come off ONE resolution — the uncapped run-feed scan this route already pays — so asking here costs one prefix listing over the proceedings table rather than a second scan.

GET /api/estate/stimulus/run-for-diff/{diff_hash}

Deterministic, uncapped lookup of the stimulus run that stimulated this comparison. Successful responses include data with the full run row (summary.steps ledger, rounds, completion/outcome, and terminal verdict) when a stimulus run references the diff, or data: null when the comparison was never stimulated — the common case, not an error. Read-only: never mints a run. Use this when the recency-shaped GET /api/estate/runs feed may have dropped an older stimulus run for a specific comparison; the comparison-surface Stimulus chapter renders per-round beats directly from this row.

GET /api/estate/knowledge-dossiers

Every facet is optional; results are deduped failure patterns, most-recently-seen first. List rows include title and meaning (plain-language labels), engine-pair fields, verdict, status, seen_count, and seen_refs — not the Markdown bodies.

GET /api/estate/knowledge-dossiers/{dossier_id}

Successful responses include data with both rendered Markdown views: estate_view_md (full estate-specific write-up) and redacted_view_md (redacted, safe-to-share pattern document). resolution_md — how the pattern was resolved / licensed / accepted — is present only later in the lifecycle.

GET /api/estate/rulings

Successful responses include data as a list of ruling fold rows (direction, licensor, flags, scope coordinates) for findings the caller may see. The provenance chain is omitted — read it through GET /api/estate/rulings/{ruling_id}.

GET /api/estate/rulings/{ruling_id}

Successful responses include data with the fold row plus its full events provenance chain.

POST /api/estate/rulings/settle

Jurisdiction components below estate_id are optional (most-specific-wins); a sub-estate component without an estate_id is refused at the store seam. The licensor identity is taken from the authenticated session, never from the request body (there is deliberately no decided_by field). Successful responses include data with the ruling fold row plus its provenance chain.

POST /api/estate/rulings/{ruling_id}/ratify

The ratifier identity is taken from the authenticated session, never from the request body. Successful responses include data with the ruling fold row plus its provenance chain.

POST /api/estate/rulings/{ruling_id}/unsettle

Withdrawal is supersession, not delete — the slot reverts to escalate and the withdrawn direction stays reconstructable from the event log. The decider identity is taken from the authenticated session, never from the request body. Successful responses include data with the ruling fold row plus its provenance chain.

GET /api/estate/ruling-questions

Successful responses include data as a list of ruling-question rows (build-time clarifications filed mid-build). Only questions the caller may see are returned: questions about estates no visible comparison touches, and installation-wide questions, are omitted except to a principal with manage_all_connections.

POST /api/estate/ruling-questions/{question_id}/answer

Answering settles the underlying (class_id, jurisdiction) slot and closes the question. The decider identity is taken from the authenticated session, never from the request body. Successful responses include data with question (the closed question row) and ruling (the settled ruling).

GET /api/estate/court/dockets

Successful responses include data as a list of docket fold rows (docket_id, class_id, neutral_question, materiality fields, and related metadata), ranked by materiality. An estate the caller may not see returns an empty list; same-class rulings from other estates are included only when visible to the caller.

GET /api/estate/court/dockets/{docket_id}

Successful responses include data with docket (the prepared package, with same-class rulings from other estates limited to those the caller may see) and court_record (attorney-chat exchanges recorded as ruling provenance). A caller who cannot see the requested estate receives the same 404 as for an unknown docket.

GET /api/estate/court/dockets/{docket_id}/audit

Successful responses include data with docket_id, class_id (which can be null when the docket carries no class), status (open, provisional, or settled), and ruling (the settled ruling when present, otherwise null). A docket with no attached class is reported as open without a ruling. The path docket is the scope of the lookup: the endpoint does not audit an unrelated class from the query. It returns 404 when the estate has no such docket, the caller cannot see the estate, or a supplied class_id does not match the docket’s own class. A settled ruling the caller cannot see is reported as status: "open" with ruling: null.

POST /api/estate/court/dockets/{docket_id}/decide

The deciding human is the authenticated caller — never the body (there is deliberately no decided_by field). A successful decision emits a ruling into the estate ruling library and binds it to the docket’s court record. Successful responses include data with docket_id, provisional, and ruling (the emitted ruling fold row).

POST /api/estate/court/dockets/{docket_id}/adopt

The adopter identity is taken from the authenticated session, never from the request body. Successful responses include data with docket_id, provisional (false), and ruling (the emitted ruling).

POST /api/estate/court/dockets/{docket_id}/chat

Records one exchange in the docket’s attorney chat (the court record). Attribution comes from the authenticated caller; auth-off callers are labeled local operator (auth disabled). Successful responses include data with exchange (the recorded row) and court_record (the full running record).

GET /api/estate/improvement-docket

The improvement docket is the second kind of queue shown in the Court panel. It contains candidate_improvement deviations held for human review, across all estates; unlike the estate-scoped ambiguity queue, these entries have no materiality ranking or estate_id filter. The list includes only entries raised by comparisons the caller may see, with visibility applied before pagination. Successful responses include data as a list of entries with docket_entry_id, finding_key, element_ref, status, source (adjudicator or builder), mode (pocket or blocking), the build and declared behaviour, clause_ref, value and risk assessments, proposed or recorded waiver_owner and waiver_expires_at (null when no terms were recorded), review fields, and action_review: the entry’s finding’s current Action Review request (proposal_id, status, live, decided_by_label, decision_reason, url), or null when the finding was never asked for. A declined request stays listed with its reason until the finding is asked for again. Entries are ordered with open work first, then newest first within each status. The Court panel displays open entries in an Improvement docket (all estates) section.

POST /api/estate/improvement-docket/{docket_entry_id}/accept

The reviewer identity is taken from the authenticated session, never from the request body. Accepting is the licensing act and requires a future expiry; for an otherwise acceptable entry, a missing, unreadable, or passed expiry returns 400 without writing the acceptance. The entry is recorded as accepted, and the finding-wide waived licence is written only when every element of the finding has been accepted. Until that point, an accepted element’s register_entry_id remains null; when the last element settles, all element rows receive the finding’s disposition:<finding_key> register entry id. Successful responses include data with the updated docket entry.

POST /api/estate/improvement-docket/{docket_entry_id}/request-approval

Asks Action Review to accept every open element of the entry’s finding in one request of type prover.decide_improvement_docket_entry.v1. The agent that filed the entry stays its proposer; the authenticated human caller is the requester and may approve the request. The agent’s filing now attempts to open this request automatically; use this endpoint for a person’s ask when automatic opening failed or an earlier request ended. The request seals its waiver owner and future expiry for approval; if the filing supplied no usable expiry, provide one here. A new request expires after seven days. Its sealed request names each open element, the finding’s current disposition, and the licence approving installs (waived, citable as disposition:<finding_key>). When another element of the finding was already rejected, the request says instead that no licence will be installed, because a finding is licensed only when every element is accepted. Approving accepts every element in one transaction and settles the licence once every element of the finding is accepted. Declining closes the request with its reason and rejects nothing: the elements stay open and unlicensed, and the finding can be asked for again. The request is withdrawn if an element is edited or decided elsewhere, or the finding’s disposition moves, before it is approved. Successful responses include finding_key, proposal_id, status, url, and replayed (true when a request still asking for the finding answered instead of a new one being made).

POST /api/estate/improvement-docket/{docket_entry_id}/reject

The reviewer identity is taken from the authenticated session, never from the request body. Rejecting closes the entry as rejected and writes neither a licence nor a finding disposition. Successful responses include data with the updated docket entry.

POST /api/estate/snapshots/{snapshot_id}/recharacterize

Submit-and-return: the response returns immediately with run_id, top-level status: "running", and data containing { "success": true, "run_id": "...", "status": "running" }. The snapshot is authorized before its boundary is read or a run is created. Follow progress live over /api/ws/estate/live?run_id=<run_id> or poll GET /api/estate/runs/{run_id}. Re-characterization applies the same caller-scoped resume rule; see Prover identity and resume isolation.

POST /api/estate/candidates/{candidate_id}/adjudicate

Successful responses include candidate_id and disposition. The adjudicator identity is taken from the authenticated session, never from the request body.

POST /api/estate/contracts/{contract_id}/seal

Successful responses include contract_id and sealed_by (authenticated human). The sealer identity is taken from the authenticated session, never from the request body.

GET /api/estate/overview

This endpoint does not document any request body or query fields. Successful responses include data with a cross-estate rollup computed in one call (so the GUI does not fan out per-scope or per-contract requests):

POST /api/estate/jitter/clone

Returns run_id (poll via GET /api/estate/runs/{run_id}) and a data summary.

POST /api/estate/proofs

Submit the Prover’s single comparison trigger for a characterized source and target pair. The comparison always runs. If spec_hash is supplied, the same proof then grades that sealed specification against the comparison it just created; callers do not need to submit a comparison first or pass its id to a second request. The proof envelope owns the child runs and records their ids in summary.comparison_run_id and summary.grade_run_id. title (body, string or null, maximum length 200) is the proof’s business title — for example Foundry aviation: raw → ontology rebuild (spec-mediated) — stamped on the proof and child runs’ provenance.title and shown as the comparison’s headline in the dashboard, above its technical source → target label. Omitting it, or sending a blank one, is deprecated: the request is still admitted, and the response carries Deprecation: true and a Warning header saying the title will be required. The agent tools prove, compare and grade already refuse an untitled call. The title is a display label, not part of request identity; when an existing proof answers the submission, its original title remains on the stored comparison. For the title shown in a no-spec response answered by a spec-titled comparison, see the system-diff reuse behavior. A new proof submitted without spec_hash uses the no-spec title rule described for system diffs. The estate reads run as the authenticated human who submitted the proof, not as the bot acting for that human. This matters for personal connections: the connection must be readable by that submitting identity. The proof still records the bot as the acting principal where the run contract needs it, so access identity and actor identity remain distinct. For a graded proof on completely enumerated snapshots, Genesis checks whether the sealed scope can be paired before submitting the run. When a carried same or changed entry names no object in either snapshot, or a source object is under a schema absent from the target and neither schema_map nor object_map addresses it, the request is refused with no proof submitted. Correct an unresolved scope name and reseal the spec, or declare the mapping and submit again. A target_model or a ratified addressing rule for the pair can supply the addressing instead; the scope verdict guide describes how the same missing-map cause appears in a completed grade. The proof envelope also preserves the pair it certifies as source_snapshot_id and target_snapshot_id, and carries the comparison verdict counts at summary.comparison_counts. On the undeclared-target-shape path, the unlicensed and indeterminate values count published readings. withheld_unlicensed and withheld_indeterminate report the corresponding readings measured but withheld from published findings; errored reads remain measured in errored_reads. Read these fields from the proof itself when presenting the result; they remain available after the child comparison run is complete. For a proof submitted by Foundry migration verification with a translated pipeline’s lineage record, summary.comparison_counts.indeterminate_causes can include non_reproducible_by_construction. This cause identifies readings of columns recorded as freshly randomized on every run; the lineage record is supplied internally from the translated script, not as a POST /api/estate/proofs request field. See the Prover comparison guide for the comparison behavior. summary.comparison_counts carries the four assumption disclosure fields described in the Prover comparison guide, including the post-assumption residual counts. It also carries withdrawn_assumptions: the assumptions withdrawn for the estate pair when the comparison ran, each with id, statement, withdrawn_by, withdrawn_by_kind, withdrawn_at, reason, named_in_would_be_licensed_by, findings_returned and readings_made_indeterminate. It is [] when nothing was withdrawn and null for a comparison recorded before the list existed. A withdrawn target-platform fold returns its missing and surplus findings tagged with that platform license, so its findings_returned is counted; other non-rule withdrawals that license no named finding retain null. The block also carries unwitnessed_by_identity with findings, objects, and sentence. It counts unlicensed findings for which no witness row can be localized because their objects have no declared row identity. sentence is the user-ready consequence and remedy; it is null when no findings are affected. The whole block is null for comparisons recorded before this disclosure existed. When the target is shaped like a Data Vault but the sealed spec omitted target_model, the proof summary also carries summary.undeclared_target_shape. It identifies the detected shape, the model to declare, and the number of readings withheld from published findings. The same key carries guidance for a non-vault pair whose objects are unpaired on both sides: it names those objects, shows shared columns as an observation, and gives an empty mapping skeleton. Read this guidance before reporting comparison counts; it is absent when there is no undeclared correspondence to explain. The proof summary may also carry summary.unaccounted_sources; its field semantics are defined in the Prover mapping guide. It may carry summary.unassessed_sources when the sealed mapping lists source tables the owner left unassessed. The object contains objects (the source-table names), count, missing (unlicensed table-level object_missing readings on those tables), basis (the owner’s ruling), and statement (a reader-facing explanation). These readings remain recorded but are not counted as findings about the build. See the [Prover mapping guide] (/docs/Genesis-Prover/Overview#declared-target-mappings-and-data-vault-shapes) for when a table may be left unassessed. A graded proof with spec_hash and no relocation premise also records summary.proof_addressing; see re-proving a spec for its fields and inheritance rules. The sealed artifact may also contain the optional top-level target_model block. It is read only after the artifact seal is verified, so it is not an additional request field and cannot be supplied separately. In schema_version: "2", its derived entries name computed targets, their source objects, required columns, derivation text, and optional grain; a mapping may contain derived targets without any splits. See Declared target mappings for the optional unassessed field and how declared mappings change pairing; a spec without the block retains ordinary one-to-one comparison. When a sealed spec declares target_model, a comparison made without that model and later named by a grade is recomputed under the model before grading. The finished grade’s provenance carries comparison_recomputed_under_spec with the replaced comparison hash, the target_model_hash, and the declaring spec hash. This adds a full comparison of both estates; a pinned comparison carrying a caller-declared row identity cannot be recomputed and returns 409, directing the caller to use POST /api/estate/proofs with the spec and row identity together. The recomputed comparison’s title comes from the grade run when recorded; otherwise, the replaced comparison’s title is inherited only when it was recorded for the same spec. See the Prover guide for title inheritance and the no-title behavior. This is an asynchronous, warehouse-bound operation: comparison-only proofs read both characterized estates, and graded proofs also read both estates for the obligations. The response returns one proof run_id; poll GET /api/estate/runs/{run_id} or envelope.links.poll. A repeated request for the same pair, addressing, column_map, spec, and round_n reuses a finished result only while the pair’s licensing is unchanged. If a correspondence-rule binding changes or a bound rule is re-qualified after the held proof started, Genesis recomputes the proof instead of returning the held result. An identical request whose grade is still in flight is superseded and names the run holding the answer. A grade that declines a seal or tier rolls up to refused, which is a terminal system decision rather than a crash. Proof reuse is also scoped to the exact acting bot and authenticated human that submitted the request. A repeat from a different bot or user starts a separate proof instead of disclosing the earlier proof result, even when every estate and proof field matches. The optional Idempotency-Key header is part of the proof identity. The proof forwards round_n to its comparison, but deliberately does not forward that header: a submission id scopes the proof, not the measurement. If the comparison is already held for the pair, the proof summary reports comparison_reused: true and comparison_reused_basis says that the id was scoped to the proof and the held comparison answered it. Change round_n when the measurement itself must run again. For the spec_hash-specific omission behavior of row_identity and case_insensitive_object_names, see re-proving a spec. When declared_relations is supplied, the names are the relocation destination, not a second object map. The first round whose target snapshot holds every declared relation binds the proof to that target; later rounds compare against the same bound snapshot instead of taking a new one. A round where a declared relation is still absent is a normal failed proof naming the missing relations, not a hold. Because the binding is defined by round order and a fixed target snapshot, this mode requires an explicit round_n and both snapshot ids; boundary hashes alone are rejected for the relocation premise. The source must also hold the declared relations, or a greenfield sealed spec must declare them in its structural-item loci; the target must not have held them in the pre-build baseline. See Relocation proofs bind once for the evidence rules. The prior-binding read uses an indexed key for this relocation premise rather than scanning unrelated proof history. It examines at most 200 rounds for the premise; that window is scoped to the premise, not to all runs on the instance, so a repair loop should remain below 200 rounds for the baseline binding to remain in the lookup window. When row_identity is supplied, its shape is validated when the proof is submitted, but the live key probe belongs to the comparison that the proof commissions. A key that is absent, non-unique, or NULL-bearing on either side refuses that pair and identifies the side, key, and duplicate count; the proof is not silently retried as an identity-free comparison. A verified declaration is recorded as the pair’s row_identity_basis, contributes separately to summary.row_identity_evidence, and controls witness localization. It does not change column comparison semantics: those remain order-independent value-multiset digests. The response includes success, run_id, status, envelope, wait, reused, and superseded_by. The common envelope is the canonical poll shape; its summary also carries proof loop fields such as round_label, rounds_used, regressions, findings, unmeasured, unmeasured_elements, and stop_reason. unmeasured is the obligation population; unmeasured_elements is the separate comparison-element population whose reads did not happen. Each unmeasured_elements entry includes element_id, comparison, object_name, optional source and target columns or target object, an optional coded cause, and the read basis. unmeasured_elements_basis explains whether the list is a measured empty list or an unmeasured population. stop_reason is a classifier token, not prose; stop_reason_detail carries the human-readable explanation. Every proof summary keeps these keys present while running, using null plus a basis when the value is not measurable yet. The submit response also copies reused onto envelope.reused; it is true when an existing proof answered the submission and false when a new proof was started. A polled envelope has reused: null, because a stored run cannot know which caller submitted the request. Terminal statuses are succeeded, failed, refused, cancelled, and superseded. When change_expectation is supplied without spec_hash, summary.declared_change_expectation carries the declaration’s change_expectation, declared_by, basis, and meaning. A must_match declaration changes the clean no-spec next call to propose, because the unchanged target answers the declared lift-and-shift premise; a must_differ declaration does not license an empty difference set, so the clean no-spec next remains author_spec. If a supplied declaration disagrees with the sealed spec’s item shapes, the request returns 422 instead of grading it. With spec_hash, meaning names the attached spec: its items say which differences are intended, and the one-word declaration licenses nothing beyond them. In run classifications, classification.model_licensed is the subset of classification.platform_licensed accounted for by the declared target model rather than by a ratified platform rule. Relocation summaries expose premise_basis and the missing-relations basis; see Relocation proofs bind once for their values and refusal behavior. With wait_seconds, the route returns 200 if the proof terminalizes during the wait. If the wait expires first, it returns 202 with the same handle and wait.expired: true; the proof is not cancelled. Validation failures, including a missing side, an invalid round, or a spec without both connection ids, return 422. An unknown snapshot or boundary returns 404; an identical proof already being created can return 409. Before scheduling work, a graded proof validates the sealed artifact and its connections. An unknown spec_hash returns 404 with not_found; a malformed spec_hash returns 400 with validation; and an unregistered source_connection_id or target_connection_id returns 200 with a terminal refused proof run. The refusal summary has stop_reason: "connection_not_registered" and puts the human-readable explanation in stop_reason_detail; it lists the sides and ids in summary.unregistered_connections. No comparison or grade is created for that refusal. After registering the missing connection, submit the same request again. When tiers is supplied with spec_hash but does not include "structural", the proof is refused before either estate is opened. A proof always creates a comparison and grades by that comparison’s diff_hash; only the structural tier reads that hash. The response is 200 with a terminal refused proof run whose summary has stop_reason: "grade_reads_no_comparison", stop_reason_detail, and requested_tiers; no comparison child or grade child is created. Add "structural" to tiers to grade the comparison, or omit spec_hash to run the comparison alone. Both this refusal and an unregistered-connection refusal carry request_key: null, so they are terminal outcomes rather than idempotency answers for a later corrected submission. When the sealed spec declares a target_model, the proof and comparison summaries expose target_model_hash; the comparison also exposes split_key_evidence. The model-aware element and finding semantics are defined in Declared Data Vault target shapes. The comparison may publish an indeterminate column_content_digest finding with cause="object_unmeasured" when a requested column checksum could not be read; see the Prover comparison guide for the reporting conditions. A proof failure is classified separately from a grade-child failure. A proof that cannot be scheduled, or whose comparison or relocation orchestration raises, uses stop_reason: "proof_faulted". When the grade child reaches a terminal non-success, the proof carries the corresponding reason: grade_died for failed, grade_declined for refused, grade_claimed for superseded, and cancelled for cancelled. The detail is the grade’s recorded error when one exists; otherwise it explains the terminal status. These tokens let callers group terminal proofs without inspecting the child run. If the server loop stops during proof admission, the service returns an unavailable response rather than parking a proof that cannot run. A stop detected before hand-off means that nothing was admitted and the request can be retried after the replacement server is serving. A stop detected after hand-off is indeterminate: a store write may already be in flight. Once the server is serving, resubmit the identical request with the same client_request_id (or the same pair and round_n when no request id was supplied). Admission checks that request key and returns the run if one was created, rather than starting a second proof run.

POST /api/estate/system-diffs

title (body, string or null, maximum length 200) is the comparison’s business title — for example Foundry aviation: raw → ontology rebuild (spec-mediated) — stamped on the run’s provenance.title and shown as the comparison’s headline in the dashboard, above its technical source → target label. Omitting it, or sending a blank one, is deprecated: the request is still admitted, and the response carries Deprecation: true and a Warning header saying the title will be required. The agent tools prove, compare and grade already refuse an untitled call. For a newly created no-spec comparison, a closing parenthetical that names a spec, such as (spec X), is omitted from the stamped title because the comparison was not graded against that spec. If nothing remains, the comparison is untitled; when inherit_pair_title is enabled, it can inherit the pair’s eligible title instead. Live re-reads performed by a comparison use the authenticated submitter’s connection access, not the agent identity that may have commissioned the run. The agent remains in the run’s provenance; a personal connection must be readable by the submitting user. The result may include an unlicensed addressing_incoherence finding for columns whose names look like convention-only renames. It is a suggestion, not a pairing; the Prover comparison guide describes when it appears and how column_map resolves it. Each side must be identified by exactly one of *_snapshot_id or *_boundary_hash (a snapshot id wins when both are given). If a boundary hash is used but no characterization snapshot exists for that boundary, the request fails with 400. Submit-and-return (like POST /api/estate/characterize): the response returns immediately with run_id, top-level status: "running", and data containing run_id, status, pair_fingerprint, component_pair, source_snapshot_id, and target_snapshot_id. The mediated diff (forward + backward, content comparison, findings reconciliation) runs as a background task. The returned pair_fingerprint is the scope-independent identity of the resolved snapshot pair used for live event routing; after the pair’s scope changes, it can differ from the observed <source_boundary_hash>-><target_boundary_hash> pair. If no snapshot-pair identity is available, it falls back to that boundary-hash pair. Follow progress live over /api/ws/estate/live?run_id=<run_id> or /api/ws/estate/live?pair=<pair_fingerprint>, or poll GET /api/estate/runs/{run_id}. The optional Idempotency-Key request header is part of the submission identity. An identical comparison submission is answered by the run that already owns it: reused: true and reused_basis identify that run, whether it is still queued/running or has finished. An in-flight response has no diff_hash yet, so poll its run_id; a finished response includes the existing result. A failed or cancelled comparison releases the identity for a later retry, while a new submission returns reused: false. A distinct header value makes a distinct question, but it does not bypass the one-comparison-per-pair resource guard: while another question on the same pair is running, the request can still return 409. When a no-spec submission reuses a comparison whose title was recorded under a spec, the response’s provenance.title omits its closing parenthetical. This changes only the returned copy; the stored comparison title remains unchanged in the listing and on the proof that graded it under that spec. The completed comparison records diff_engine_version and diff_engine_digest. A pinned retired-engine comparison returns 409; reuse and recomputation semantics are defined in the [Prover comparison guide] (/docs/Genesis-Prover/Overview#2-comparison-the-system-diff). After the comparison reaches a terminal state, its run summary includes auto_envelope. This object reports materialized, status, run_id, and basis: materialized: true means this comparison minted an envelope or an earlier run already owns one, materialized: false means the envelope attempt failed, and null means the comparison was not measured for envelope creation (for example, the feature was disabled or the run was a stimulus differential). The per-diff hook is enabled by estate_char.comparison.auto_envelope, whose default is true; when it creates an envelope it runs a second spec_conformance grade, and it is idempotent for a given diff_hash. A normal skipped or disabled result does not fail the comparison. An identical comparison submission — the same resolved pair, addressing maps and case-folding policy, column_map, delta_hash, row_identity, quiescence attestation, round_label, and client request id when supplied — is answered by the run that already holds it instead of starting another run. The response sets reused: true both at the top level and on envelope.reused, returns the existing run_id, and reports that run’s status. A queued or running reuse has no diff_hash yet; poll the run before reading the comparison. A failed or cancelled comparison is not reused, so retrying after one of those statuses starts a new run. New submissions set reused: false; the response envelope is the row created for that run. Comparison reuse is scoped to the exact acting bot and authenticated human as well as the comparison inputs. A caller with a different identity receives a new comparison rather than the existing run’s result. A completed comparison is also not reused when a correspondence-rule binding for its pair moved at or after the run’s started_at. Ratification, pair-scoped review, or revocation records that change the binding clock therefore make a repeat submit a new comparison instead of returning the older result (for example, an earlier object_missing + object_surplus rename pair). The new run is what can report the ratified rule as object_relocated. A finished migration behavior proof for the same database-and-connection sides also ages a completed comparison; a run against another database on either connection does not. See Genesis Prover for how the run profiles identify those sides. If such a proof finished at or after the comparison started, an identical repeat recomputes instead of reusing the older comparison. Whether an individual rewritten view is licensed depends on a proof of its specific view pair, as described in fresh licensing evidence. When schema_map or object_map is supplied, Genesis resolves it against both snapshot inventories before creating the run. A completed run summary discloses the binding with schema_map_keys_submitted, schema_map_keys_bound, schema_map_keys_unbound, object_map_keys_submitted, object_map_keys_bound, and object_map_keys_unbound. These fields show which submitted keys matched the source inventory; an empty map binds nothing and is reported with zero counts. Invalid or ambiguous map entries are rejected with 400 at submission rather than producing a run whose intended pairs all appear missing or surplus. A source object declared by the target model as input-only is answered by that model, including when the build loads it into no target object, so it does not require a schema-map entry merely to avoid an unbound-schema refusal. When row_identity is supplied, its live uniqueness probe runs on both sides under the same snapshot addressing and any ratified delta used by the comparison. A verified declaration becomes the pair’s row_identity_basis and selects the key used to localize witness rows; it does not change the column comparison, which remains an order-independent value-multiset digest. For agent, headless, or stored re-run paths that perform the probe inside the diff, a key that is not unique, contains NULLs, is absent, or cannot be resolved withholds that pair behind an indeterminate row-identity element rather than silently falling back to digest-only localization. The API submission path rejects the same failure with 400 before creating a run. Probe counts are kept separately in the run’s summary.row_identity_evidence and are attributed to that run. For a pair whose binding rules declare exactly one digest quantum, the system diff re-measures approximate columns on each side when measurement versions are compatible. The re-measure is best effort: if no quantum is declared, versions are incompatible, or a side cannot be read, the comparison uses the exact snapshot digests instead. Inspect summary.quantized_remeasure on the completed run to see whether the quantum was minted, its significant_digits and rule_id, and the re-measured column count for each side. The completed summary’s findings object also reports closed_by_license separately from closed_by_reverification; a licensed difference closes under licensed_by_rule because the rule allows it, not because a repair was verified. The whole-table content digest is folded over the licensed-comparable columns. When a licensed column, or a column held by only one side, would make the whole-table digest differ by construction, the diff performs a best-effort live re-fold over the comparable columns for that object instead of treating the digest as actionable drift. This re-fold is automatic, pair-scoped, and only runs for objects the initial diff cannot decide from its snapshots; it uses one live table scan per requested object and falls back to abstention if either side cannot be read. Inspect summary.comparable_digest_refold for folded, objects_asked, objects_folded, source_objects_read, target_objects_read, failures, and errored_reads. folded is true only when every asked object folded on both sides and no read errored. failures is always present; each entry identifies the object, its sqlstate when supplied by the driver, whether the failure is retryable, and a basis. A retryable transport failure receives one bounded retry; an engine refusal is reported without retrying. A locus the comparison could not decide — an indeterminate element — closes nothing. Its open finding stays open and its lifecycle entry records coverage_qualifier: "indeterminate", kept distinct from not_exercised (the run never reached the locus) and from resolved_verified (the run swept it and found nothing). Older lifecycle events written with "undecided" are read back as "indeterminate"; the stored history is not rewritten. An abstention is not a re-verification, so it never appears in finding_counts.closed. If a comparable re-read errors, the affected element uses verdict errored and cause comparable_fold_errored; it is not counted as indeterminate, and the comparison summary reports errored_reads separately with clean: false. This means the comparison is unmeasured for that element rather than evidence of drift. Two additional causes are intentionally indeterminate rather than unlicensed: cross_representation_precision_unverified means boundary values agree to float precision but an exact numeric side may contain unobserved digits, while outside_characterized_scope means a target object lies outside the characterized source boundary and the source scan never measured the surface being compared. Completed mediated-diff payloads also include verdict_bases, a compact map of the verdict meanings and named causes present in that diff; use it when relaying why a comparison is indeterminate rather than treating all abstentions as the same. Live events stream in order: phase("diff") → family_found → element_verdict → phase("fold") → summary → done.

GET /api/estate/system-diffs

Successful responses include data as mediated-diff summaries (verdict counts only, no payload), newest first. next_cursor and has_more are always present; they are null and false when the request is the unbounded whole-board read. When limit is provided, has_more reports whether an older page exists and next_cursor resumes after the last returned row. The bound limits the response page, not the database reads used to curate pair identity and featured runs. The response also includes hidden, counting withheld comparison cards rather than withheld runs: one per estate pair (per scan scope when both endpoints belong to one estate), however many runs were withheld. A card does not count as hidden if the caller can still see a run for it. This is separate from has_more, which reports older rows available through pagination. The count can include pairs the Prover grid itself omits under sandbox or utility estates. Each row is enriched for the comparison list: ruling_rollup contains integer counts for the featured comparison’s settled residue and the pair’s open behavioral catches, plus the current Human Review settlement state for the comparison: For person_accepted_differences, only a person’s ruling or a population-bound one-time exception counts as person-settled. A finding claimed through attributed_to or rule_ids is grade-licensed, not person-settled, only when the grade re-sorted counts; a finding marked unaccounted remains outside that license. The rollup is an enrichment, not a reason to omit the comparison. If its supporting reads fail, the row is returned without ruling_rollup so clients can show an unresolved ruling state rather than treating missing data as zero. The readiness result is computed from the complete, element-bound Human Review population; it is not a count inferred by subtracting ledger totals from an element count. Only featured (is_latest_for_pair) comparison cards receive this optional readiness evaluation and the approval-movement disclosures. If the movement read fails, those fields are omitted rather than reported as a clean zero. For a comparison with a recorded settling decision, the listing uses the same population resolution as the Human Review read before reporting review_decision. This keeps the card’s approval state and the review page’s approval state aligned: an incomplete or unavailable comparison population cannot make an approval appear to stand on the card when the review read has withdrawn it. Individual diff detail (GET /api/estate/system-diffs/{diff_hash}) and dossier readers are unchanged — they still see full history regardless of archive state.

GET /api/estate/comparisons/{diff_hash}/export

Requires view_connections. The response’s data is a portable bundle with an envelope (bundle_type, bundle_version, contract, exported_at, source_server, connection_refs, estate_ids, and adjudications_excluded) and a tables map containing the comparison and the rows needed to render its card: its run family, snapshots and measured objects, estates and scans, review events, exhibited findings, the adjudication proceedings that ruled on those findings with their verdicts, compiled specs, and cited correspondence rules. Connection values are references only. Stored JSON is scanned and credential-shaped fields are replaced before export. The bundle preserves the comparison’s diff_hash; it is not a re-run and does not need the source or target database credentials. An unknown comparison returns 404. Only a terminal adjudication travels. A proceeding still running on this server is left out and named in envelope.adjudications_excluded with basis excluded_by_design, because nothing on the receiving server would ever finish it and that server’s own reconcile sweep would terminalize it as failed. bundle_version is 2; an importer that reads a different version refuses the bundle on the envelope rather than applying the sections it recognises. This is an expensive read: about two dozen indexed store reads plus JSON decoding for the returned payloads, which can be several megabytes for a real comparison.

POST /api/estate/comparisons/import/validate

Requires ratify_source_contract. This side-effect-free endpoint reports what the bundle would create or collide with, the estate ids it would adopt rather than twin, the durable finding numbers it would remap, blockers, and which referenced connections are registered on this deployment. A collision is disqualifying only when on_conflict is refuse; a missing connection is reported but does not prevent the imported card from being read. A blocker — a unique value a different row on this deployment already owns, checked against every uniqueness constraint the tables declare rather than against the row identity alone — is disqualifying under every mode, so valid is false whenever one is present. blocking_count is the exact total; blockers is a bounded sample of it, because one blocker is enough to refuse and every row of a large bundle can collide. Validation performs indexed existence reads and writes nothing. A malformed or unreadable bundle returns 400.

POST /api/estate/comparisons/import

Requires ratify_source_contract. The import writes the bundle in one transaction; it does not re-characterize either estate or re-decide the review. The original review author and timestamp remain on the imported review events, while the importing caller is recorded separately as imported_from. Estates already present for the same database boundary are adopted, not duplicated, and the response includes the source-to-target estate_id_map. A finding’s durable number (durable_id, the #N a report cites) is a per-instance sequence under a unique index, not a portable identity, so it is remapped on import — the target’s own number where the target already holds the finding by finding_key, a freshly minted one otherwise — and the response includes the source-to-target durable_id_map. The finding lifecycle events that join on that number are remapped with it. The response reports per-table created, skipped, and replaced counts, imported_from, durable_id_map, and no_op (true when nothing was written, such as a repeat import). An import collision under refuse returns 409; an invalid bundle, an invalid mode, or a uniqueness value a different row already owns returns 400. The operation can read and write tens of thousands of rows for a real comparison; it decides every row before writing any, so a refusal writes nothing at all, and any later failure rolls back all writes. It owns its transaction and commits it while holding the process-wide durable-number mint lock, so no other allocator can take the same numbers between the allocation and the commit — and a concurrent findings reconcile waits for the length of the import.

GET /api/estate/system-diffs/{diff_hash}

Successful full responses include data with diff metadata and payload.elements (each element carries comparison, verdict, and object/column context). When present, payload.row_identity_basis discloses the identity basis for each object pair; it is separate from the run’s live uniqueness evidence. List summaries from GET /api/estate/system-diffs omit payload and expose verdict counts plus comparison-list enrichment: direction, resolved source_estate_id / target_estate_id, retired_by_archive, pair_key / is_latest_for_pair / is_latest_for_pair_basis / pair_run_count, source_engine / source_database / target_engine / target_database, and when the forward migration target is a curated highlight from the estatebench oracle (postgres or postgresql source dialect), display_name, highlight_rank, and is_highlight. A customer proof from another source does not receive a curated label even if its target database name matches a curated target. full and scope detail responses also include source_estate_id, target_estate_id, and retired_by_archive, resolved using the same endpoint-estate rules as the comparison list but without reading the whole list. Producing these fields costs one batched snapshot-identity read, one estate-list read including archived rows, and two indexed scan-timeline reads. See the [comparison list contract] (/docs/Genesis-API/API-Reference#get-api-estate-system-diffs) for field meanings. Detail responses also include errored_reads, the number of persisted elements whose verdict is errored, and clean, which is true only when no persisted element is unlicensed, indeterminate, or errored. On a comparison with undeclared_target_shape guidance, the published unlicensed and indeterminate counts exclude withheld readings; read the comparison summary’s withheld_unlicensed and withheld_indeterminate fields to report those populations beside the published counts. These values are computed over all elements before applying an optional ?verdict= filter, so a filtered response still reports the comparison-wide counts. The response also carries comparable_digest_refold from the associated run summary, or null when the diff has no run, the run is gone, or no re-fold disclosure was recorded; see the re-fold fields documented above. The response also carries value_coverage for the direction identified by this diff_hash, including when shape=scope; see the [Prover comparison guide] (/docs/Genesis-Prover/Overview#2-comparison-the-system-diff) for its counting semantics and the distinction between an unavailable disclosure and values that were all compared. It also carries object_presence and not_measured for that same direction; their field shapes and null semantics are documented in the [review population response] (/docs/Genesis-API/API-Reference#get-api-estate-comparisons-diff-hash-review-population). Only shape=full also returns which_proof_applies. It explains each rewritten view in views[] (source_view, target_view, kind, reason, and behavior_proof_run_id), counts unchanged-text views in same_text_views with a shared same_text_reason, and identifies next_view. Kinds are needs_behavior_proof, behavior_proof_passed, behavior_proof_failed, behavior_proof_did_not_run, behavior_proof_passed_data_not_compared, data_differs, and data_explained_by_rule. This is explanatory evidence from the stored comparison, not a verdict; it is null for a backward comparison. It is computed over the comparison’s elements before an optional verdict filter, and is absent from scope responses. A view whose behavior_proof_run_id is set also carries test_case_coverage: run_id, a one-line sentence, situation_count, covered_count, by_side (original and rewritten, each situations and covered), covered[] and not_covered[] (each side, view, situation, claim_tag, case_ids), not_credited[] (claim, case_ids, why) with not_credited_count, not_matched[] (names, case_ids, why: the run’s test cases with coverage claims that name neither view) with not_matched_count, listed_up_to (the most entries each list holds), and caveat. It says which logic situations of the two view texts that run’s approved test cases claimed, as the agent stated when designing them; running the proof does not check it. A test case belongs to the view its output_relation names, compared on schema and name. If the relation matches no view in the comparison, Prover uses target_node’s dbt model name only when that name matches exactly one view pair. Each list is capped at listed_up_to entries, while the counts cover the full set; at most 25 views are worked out in one read, and any additional view reports that its coverage is not known. See the [Prover comparison guide] (/docs/Genesis-Prover/Overview#rewritten-views-and-behavior-proofs) for how to act on it. Detail responses also carry populations, the comparison’s two differently scoped counts of “differences” and the arithmetic over each — the same block the read_comparison tool publishes, from the same writer, so the tool answer and this one cannot be two readings of one row. populations.elements carries total, match, differing_or_indeterminate, decided, licensed, unlicensed, indeterminate, errored_reads, and basis; elements.total is the whole denominator (match + licensed + unlicensed + indeterminate + errored_reads) while differing_or_indeterminate is the narrower subtotal — adding the count columns yourself produces the second and reads as the first. populations.findings is the separately scoped ledger population and its total is null here because this route makes no findings read, and populations.why_they_differ says why the two must never be added. It is unconditional and costs nothing: every number is read off the row this route already built, and a count this shape did not make is null rather than a derived zero. The same detail response carries writer_attribution counts and the per-relation writer_readings used to derive them for target relations. The counts include the relations denominator plus attributed, unattributable, and unmeasured; the qualifier is null only when every relation was attributed to a re-runnable mechanism. A reading attributed from a Genesis Flow run also includes flow_run with flow_id, flow_name, flow_version, run_id, step_name, step_run_id, step_started_at, and step_completed_at. shape=scope omits writer_attribution and writer_readings because it does not read payload. The detail response also carries the assumption disclosure from the run that minted the comparison; see the Prover comparison guide for field semantics. When an assumption is withdrawn for the comparison’s estate pair, later comparison responses disclose it in withdrawn_assumptions. The withdrawal set is part of the next comparison’s request identity, so a comparison made before the withdrawal is not reused. The request identity and the withdrawal records used for the comparison are taken from one snapshot, so the run’s withdrawal bylines cannot change if the standing changes after the request is keyed. The stored verdict of an earlier comparison is unchanged; a sign-off whose pass relied on the withdrawn assumption is reported as reopened when review state is read. A later sealed specification records the withdrawals in its provenance as assumption_withdrawals, and read_spec exposes those citations under who_decided.

GET /api/estate/system-diffs/{diff_hash}/assumptions

The response’s data contains diff_hash, pair_fingerprint, component_pair, and an assumptions list. Each entry has id, statement, withdrawn, and withdrawal; withdrawal is null when the assumption stands, otherwise it includes id, statement, withdrawn_by, withdrawn_by_kind, withdrawn_at, and reason. The list contains the pair’s rule-borne assumptions and the target platform’s identifier-case and engine-collation licenses when this comparison relied on them. It can also include the built-in PostgreSQL type assumptions, the verified unique-index limitation, and the behavior-proven translated-view and source-built-output assumptions. Their ids include identifier_case:<platform>, collation_ordering:<component_pair>, platform_type:<component_pair>:<type-rule>, platform_cannot_enforce_unique:<platform>, translated_view_behavior_proven, and source_built_output. The Prover guide documents the conditions behind the new comparison assumptions.

POST /api/estate/system-diffs/{diff_hash}/assumptions/{assumption_id}/withdraw

Send an optional JSON body { "reason": "..." }. The response’s data contains event_id, pair_fingerprint, component_pair, assumption_id, action: "withdraw", the actor fields, reason, reopened, and created_at. reopened records the comparisons whose pass relied on the assumption at withdrawal time; it does not persist a sign-off status, which is recalculated when review state is read.

POST /api/estate/system-diffs/{diff_hash}/assumptions/{assumption_id}/restore

Send the same optional reason body. The response has the withdrawal response shape with action: "restore". A restore affects later comparisons; a sign-off reopened by a prior withdrawal stands again when the withdrawal is no longer in force and no other relied-on assumption reopens it. The full payload also includes licensed_breakdown, which gives the licensed element count by comparison and the provenance counts that contributed to it, and object_measurement_keys, the measured row-count/boundary fingerprints used to support unambiguous rename hypotheses. These derived disclosures do not change the diff hash. Case-folded column correspondence is recorded as licensed column_relocated elements. For categorical columns whose value-count key spaces cannot be lined up because float-rendering coverage is split or unknown, the element is indeterminate with cause="categorical_recipe_mismatch"; its relied_on.basis distinguishes recipe_split_unmeasured from recipe_unknown. For a comparison created by a sealed model-aware proof, the detail response also includes target_model_hash and split_key_evidence. Split elements carry provenance: ["model:split"]; model:ghost identifies declared ghost rows excluded from hub key readings, and model:metadata identifies columns derived by the vault load rather than carried from the legacy object. The comparison does not silently compare a legacy object to a satellite or link’s derived rows; those image contents remain unmeasured and the response explains that basis. The detail response also includes model_readings for model-aware comparisons. It maps each legacy object to the comparison kinds this run actually decided and the target objects read for them. An open finding on that object and kind remains not_exercised when the map has no such reading or does not include the finding’s target; absence from the current result is not treated as a re-verification. Use shape=scope when paging the comparison’s findings and the element document is not needed. The scope response identifies the comparison and carries its four stored counts, but omits payload, errored_reads, and clean rather than reporting unmeasured zeros. Every response names its shape in comparison_shape.

GET /api/estate/system-diffs/{diff_hash}/families/{family_signature}/members

Successful responses include data with family_signature, total, offset, limit, and members (each an annotated finding with its evidence).

GET /api/estate/fault-drill-coverage

Successful responses include data with available plus, when available is true, headline stats (coverage, obligation_coverage, drills_total, caught_total, survived_total, blind_spot_count, and related fields) and the full report (by_class per-mutation-class coverage, blind_spots, results). When no fault-drill run has produced a report for this snapshot yet, data is {"available": false} — trigger POST /api/estate/estates/{estate_id}/fault-drill on demand (the static meter no longer runs on every scan by default).

GET /api/estate/behavioral

Legacy pair-scoped trajectory-campaign detail. Successful responses include data with available plus, when available is true, component_pair, diff_hash (the comparison the campaign was run against, null when none), reproducible, findings_count, candidate_engine, step_count, and verdicts — per-step outcomes (PASS / FAIL / MISSING with row-count and digest agreement) from feeding identical synthetic streams into both estates. When the pair has no trajectory-campaign run, data is {"available": false}. A component pair names engine types (for example postgres->databricks), which every comparison between two such estates shares, so this route can return a campaign about a different estate’s objects — anything rendered on one comparison must use GET /api/estate/behavioral/steps-for-diff/{diff_hash} (per-step markers) or GET /api/estate/behavioral/chip-for-diff/{diff_hash} (verification chip).

GET /api/estate/behavioral/steps-for-diff/{diff_hash}

Diff-scoped per-step behavioral outcomes: the trajectory campaign run against this comparison (matched on its summary.diff_hash, never on the component pair) and its per-step agree/diverge verdicts, which back the comparison’s behavioral markers. Same data shape as GET /api/estate/behavioral; only the resolution rule differs. When no campaign was run against this comparison, data is {"available": false} and the UI renders no markers. Read-only.

GET /api/estate/behavioral/chip-for-diff/{diff_hash}

Diff-scoped, verdict-backed behavioral chip for a comparison. Resolves the stimulus run that stimulated this diff (same resolver as GET /api/estate/stimulus/run-for-diff/{diff_hash}) and reflects its authoritative verdict — not any trajectory that merely shares the estate pair. Successful responses include data with available plus, when available is true, verified (true only for a finished verdict with grading mode spec_graded or baseline_subtracted and unlicensed_count == 0; null/ungraded/running never verify), grading_mode, verdict (matched_count, licensed_count, unlicensed_count, indeterminate_count), status, run_id, diff_hash, rounds_succeeded, escalation_tier, and finding_severity. When the comparison was never stimulated, data is {"available": false} and the UI renders no chip. Read-only.

GET /api/estate/stimulus/preflight

Provide diff_hash, or both source_snapshot_id and target_snapshot_id. Side-effect-free feasibility probe the compare / escalation dialogs call on open. Successful responses include data with possible (false on a hard blocker — feature off, unresolvable comparison, unreachable connection, unreadable registry, no configured executor/agent, no provable ETL for an agent-driven executor, or a pair contract that is stale or displaced), requires_override (true while possible stays true when a side is a live non-clone estate — the GUI requires an explicit in-place-injection confirmation), etl_provable (whether the comparison’s estates have a registered, cloned ETL code asset the stimulus can execute — always true for non-agent executors such as legacy-bench), reasons (named conditions/blockers), pair_contract (the resolution path, contract_hash when one is authoritative, stale, drifted, and any unverified_at_submit identities), and resolved context (diff_hash, component_pair, source_estate, target_estate). pair_contract.resolution is ratified, lineage, legacy_default, or displaced. Each reason carries code and message; per-side connection blockers also include side (source or target); requires_override reasons may include estates (named non-clone estates). Reason codes include feature_disabled, unresolved_comparison, requires_override, executor_unconfigured, etl_not_provable, agent_unconfigured, connection_unreachable, connection_registry_unreadable, pair_contract_missing, pair_contract_stale, and pair_contract_displaced. A missing contract warns when the legacy default can address the pair and hard-blocks when it cannot. When etl_not_provable, the message names the missing code asset and how to register and clone it. Read-only — never allocates a run or commissions an agent. An unresolvable comparison returns possible: false with a reason instead of HTTP 404.

POST /api/estate/stimulus/run

Provide diff_hash, or both source_snapshot_id and target_snapshot_id. Submit-and-return: an AI-designed synthetic edge batch is injected identically into the source and target clones, the downstream ETL is re-run on both sides, and the two worlds are handed to the deterministic differential comparison. A ratified or lineage-resolved pair contract supplies each input table’s logical-to-physical relation mapping, per-side column spellings, and island locator; the submission tools validate rows against each declared island before accepting them. Machine-checkable spec loci are resolved through that same contract: the logical locus remains the comparison identity, while each side’s declared physical relation, columns, and key predicate are used for SQL. A contract-less pair uses the legacy default, which binds its registered batch-manifest inputs late, uses the legacy id-column floor (id >= 900000), and discloses that provenance on the run. In contract mode, the ETL reply must preserve statements_run as separate source and target lists; provenance is verified against each side’s declared assets, and the pair is verified only when both sides pass. The response returns immediately with run_id, top-level status: "running", executor, and data. The default executor commissions a minutes-long Genesis agent mission (watchable over /api/ws/estate/live?run_id=); the designed batch, per-step evidence, agent thread id, completion report, and triggered follow-up land on the run row. Poll GET /api/estate/runs/{run_id}. When the run finishes, its summary.outcome includes aggregate prediction_counts, which discloses checked, confirmed, confirmed_source_oracle, confirmed_authored, violated, unreliable, unconfirmed, unpredictable, review_routed, malformed, and integrity_failures. A round can verify only when its sealed predictions are fully accounted for, have no unjudged, review-routed, or malformed entries, and either contain no sealed predictions or include source-oracle confirmation; authored-only matches are disclosed but do not provide confirming authority. Predictions that cannot be mechanically judged remain disclosed as non-confirming outcomes rather than being treated as a pass.

GET /api/estate/findings

The response always includes data, next_cursor, and has_more. next_cursor is null on the final page; otherwise pass it as cursor to continue. Both rows and rulings use the same keyset, so view=rulings is bounded and resumable too. The rulings projection includes created_at as its resume anchor and still omits evidence and systemic-family annotation. The server reads one row beyond the requested page to set has_more; it does not count the full ledger. Each row includes finding_key, component_pair, comparison, object_name, optional column_name, status, disposition fields when set, evidence, and additive systemic-family fields when present: family_signature, family_direction, and family_pattern. family_signature_shape is persisted when those coordinates came from the finding row’s stored family stamp, or computed when they were derived from its evidence document for this response. Both views carry superseded_by_pair, the pair now used by the finding’s identity class when the row’s pair was superseded after a scope change; it is null when the row’s pair is still current or cannot be placed in a class. This read-time label is stamped after visibility filtering, so identity reads are made only for distinct pairs among the rows visible to the caller. For behavioral prediction findings, evidence preserves the measured column separately from the row selector. missing_row and predicted_value evidence includes row_key (the exact selector), key_column, key_value, predicted_value, source_value, and target_value; for missing_row, target_value is null and target_absent_reason is no row produced. When a side’s read matched one row whose cell was SQL NULL, measured_null lists that side (source or target); the receipt renders the cell as NULL, distinguishing a measured NULL from a value that was not read. detail remains a human-readable predicted-versus-actual receipt. Use view=rulings when joining recorded rulings to comparison rows. It applies the same component_pair and status filters while avoiding evidence decoding and systemic-family computation. updated_at is the ruling revision token for correlating this projection with a separate read that contains author or note details; created_at is the page-resume anchor. status is the finding lifecycle, so a request with status=all includes closed findings; disposition_note carries the grounds for the recorded verdict. The comparison listing uses this projection server-side to populate ruling_rollup before returning each row. The list routes do not emit the evidence document unless include_evidence=true is requested on view=rows; the server still reads it internally when computing systemic family annotations. For a complete ledger, follow next_cursor rather than expecting one unbounded response. When include_consult=true, rows with a Prover Consult ruling also include consult_ruling, or null when none exists. Its state is one of recorded, pending_approval, provisional, ratified, or declined; actionable indicates whether approve or decline is currently available. The ruling_digest_id identifies the exact ruling a human read and must be sent to either decision endpoint. Use the detail endpoint below to load the rationale, probes, guard, and constitution before deciding.

GET /api/estate/findings/{finding_key}/consult-ruling

Returns the current Prover Consult state and the full ruling body for one finding. The response includes finding_key, finding_id, ruling_digest_id, state, actionable, verdict, apply_mode, finding_sha, ruled_at, any decision metadata, and ruling with the seat’s rationale, probes, guard, and constitution version. It also carries requested_by_kind (agent, service, or null when the ruling was recorded before its origin was), requested_by_bot_id, decides_through_action_review, and action_review — the linked Action Review request (proposal_id, status, url, decided_by_label) or null. This is the read to use before approving or declining. It returns 404 when nobody has consulted about the finding.

POST /api/estate/findings/{finding_key}/consult-ruling/request-approval

Asks for Action Review approval of the recorded ruling. A ruling an agent or a service asked for — or one recorded before its origin was — is approved or declined only through this request (prover.approve_consult_ruling.v1); the approve and decline endpoints below answer it 409 with code: consult_requires_action_review. The caller becomes the requester and may decide the request. Idempotent: a request already asking is returned with replayed: true. Requires view_connections and a signed-in person when authentication is enabled; any person may request and decide it, with no special approval permission. A person’s own PAT is accepted and attributed to its owner. The response carries proposal_id, status, url (where a person decides it) and replayed. 409 with code: consult_approval_declined when a person (or a policy) declined its request, including after that decline is recorded on the finding — the ruling is closed and the next step is a new Consult ruling — code: consult_ruling_decided when the ruling was otherwise already decided, and a plain 409 when the digest is stale. 503 when the Action Review ledger is unavailable.

POST /api/estate/findings/{finding_key}/consult-ruling/approve

Approves the recorded ruling. Under human_approval, this seeds and ratifies its direction in one action. Under auto, it ratifies the provisional direction. Any signed-in person may approve or decline the ruling, including the person who requested it, without a special approval permission. The route requires view_connections; the person’s own PAT is attributed to its owner. The successful data object includes decision: "approved", the resulting board: "ratified", the finding event event_id, and the ruling_id established.

POST /api/estate/findings/{finding_key}/consult-ruling/decline

Declines the recorded ruling. Under human_approval, nothing was seeded and the finding remains unlicensed. Under auto, the provisional direction is withdrawn only when it was established by this exact ruling. The request body and person-signing requirements are the same as the approve endpoint; its successful data object uses decision: "declined" and board: "declined".

GET /api/estate/findings/dispositions

The response includes findings, total, offset, limit, and by_disposition. Each finding carries its key, comparison and object coordinates — comparison, object_name, column (null at object grain), the spelling a graded drift_finding discrepancy declares, so the two surfaces join on the discrepancy’s own key; column_name carries the same value under the ledger row’s own spelling for one release — status, current disposition, disposition_note, dispositioned_by, disposition_proposed_by when an agent-staged package was used, dispositioned_under_model (the target_model_hash of the comparison reading the ruling judged), prover_defect_report_id, prover_defect_filing, prover_defect_filing_detail, waiver_owner, waiver_expires_at, last_seen_diff, updated_at, and superseded_by_pair. The latter is the pair now used by the finding’s identity class when its recorded pair was superseded after a scope change; otherwise it is null. It is a read-time label and does not move or close the finding. For an older finding whose waiver terms were not recorded, waiver_owner and waiver_expires_at are null; the API does not infer an expiry. For an instrument_defect ruling, prover_defect_filing is delivered, user_action_required, or failed; see False alarm (Prover) for the filing-status meanings. A ruling is honored only when the later comparison uses the same target model, including null for a comparison with no model. by_disposition counts the visible filtered set before paging and uses undiagnosed for findings whose disposition is null. Findings from comparisons the caller cannot see are omitted before paging and counting; a hidden diff_hash is returned as not found. The endpoint does not return evidence or systemic-family annotations. Invalid status or disposition values return 422 rather than an empty successful page.

GET /api/estate/findings/ranked

Each row includes the flat finding fields plus blast-radius and plane annotations in rank order (highest deviation first). Evidence is included only when include_evidence=true.

GET /api/estate/findings/ledger

Returns a bounded page with data, next_cursor, and has_more. Follow next_cursor until has_more is false to walk the matching open set. The ledger is ordered by most recently minted finding first using (first_minted_at, signature), while updated_at remains available on each row for surfaces that need activity order. The watermark is the first row’s mint position on page 1; findings minted after the walk starts are excluded from that walk. A finding open for the whole walk is returned exactly once, but this is not an instantaneous snapshot: membership is evaluated when each page reads its mint position. A finding resolved before that page is read is omitted, and one that reopens after the cursor has passed its position waits for the next walk. Visibility is applied before the page limit, so each page is filled with rows the principal may see when enough remain. has_more reports whether more visible rows follow, and the opaque cursor resumes that admitted-row walk. hidden reports withheld findings encountered after this page’s last returned row in the current read window; on the final page it counts through the end of the walk. It is page-local, not a ledger-wide total. Each row is folded from the append-only lifecycle event log (estate_char_finding_events) and carries: Resolved findings (no_longer_observed) are not returned here; load one by durable number with GET /api/estate/findings/{finding_id}/history. This ledger is distinct from GET /api/estate/findings (remediation-loop rows keyed by finding_key) and from GET /api/estate/findings/{finding_key}/episodes (reconciliation-episode sightings). A cursor minted by the findings listing is not valid here because the two endpoints walk the same key in opposite directions. Ledger cursors minted before the mint-position key change must also be discarded; restart the walk without cursor after upgrading.

GET /api/estate/findings/ledger/summary

Returns counts for the open ledger without the finding rows. The response has a data object with these keys: The summary reads the materialized open-findings projection and does not fold the event log. Its by_state values can therefore lag a lifecycle transition that is still inside the ledger fold window; use the ledger or a finding’s history when you need folded state for an individual finding.

GET /api/estate/findings/{finding_id}/history

Successful responses include data with the same folded lifecycle object shape as one row of GET /api/estate/findings/ledger (including lineage for both open and resolved findings). The history is a pure fold over the append-only event log — opened rX, resolved rY, reopened rZ — so it cannot disagree with the ledger’s current state. The response also derives rests_on_reopened_seal at read time, as described in the ledger response fields. That adds one scalar read for the run that last saw the finding and, when that run names a spec, reads of its seal provenance and reviews.

GET /api/estate/comparison-events

Each event is one comparison run (A vs B at time t). Rows include diff_hash, run_id, component_pair, pair_fingerprint, delta_hash, source and target endpoint snapshots, created_at, verdict counts (licensed_count, unlicensed_count, indeterminate_count, matched_count), plus published_unlicensed_count / published_indeterminate_count and withheld_unlicensed / withheld_indeterminate. The published fields exclude readings withheld for an undeclared target shape; the withheld fields are null when nothing was withheld. Rows also include finding_counts (opened / recurred / closed), and the systemic-family rollup in families. Each family includes its signature and counts plus sample_members; each sample member carries finding_key, object_name, column_name, disposition, and an evidence object narrowed to source_value and target_value (or null when evidence is absent). The listing omits member_keys and full evidence documents; fetch one event’s findings for the complete rows. The settled_at_run object contains person_ruled and instrument_void counts from the residue this comparison measured, as they were settled when the run executed; it is null when that historical record is unavailable, not a zero count. A recorded run that only closed findings can legitimately return zero for both counts. Per-finding rows are not inlined; load them for one event via GET /api/estate/comparison-events/{diff_hash}/findings (newest events first). The response also includes truncated, which is true when more events remain beyond the page, and total_row_count, which is the exact event count only when the page is complete; it is null on a truncated page.

GET /api/estate/comparison-events/{diff_hash}/findings

The diff_hash path accepts an unambiguous 8-or-more-character prefix as well as the full digest; ambiguity and minimum-length refusals are described in the toolkit short hash contract. Successful responses include data as an object: {diff_hash, view, total, offset, limit, settlement_counts, settled_at_run, settled_at_run_basis, findings[]}. total is the count before slicing, so a caller knows how far it has to page. settlement_counts contains one count for each basis — person_ruled, instrument_void, closed_by_diff, and unsettled — across every finding the comparison exhibited, not only the returned page or a filtered family. settled_at_run repeats the parent comparison event’s historical split; null means that the as-of-run record is unknown, and settled_at_run_basis explains why. With view=rows, each entry of findings carries finding_key, comparison metadata (comparison, object_name, column_name when present), event_role (opened, recurred, or closed), decoded evidence, and systemic-family annotations when present (family_signature, family_direction, family_pattern). It also carries top-level column, detail, source_value, and target_value fields, read directly from the corresponding values in evidence rather than re-derived. Missing evidence values are null; when there is no evidence object, column falls back to column_name. Every row also carries rests_on_reopened_seal: the read-time reopen notice when the run that last saw the finding is a verdict over a seal whose ruling was withdrawn or overturned, otherwise null. The finding remains open; relay the notice rather than presenting it as a live failure of that ruling. Deriving the notice costs one indexed event query and one scalar run read per 500 rows, plus seal-provenance and review reads when any run names a spec. See the ledger response fields for the notice semantics. Every row also carries settlement: {settled, settlement_basis, settled_by, settled_by_basis, settled_at, settled_at_basis}. Read this block to determine whether the difference is accounted for; status, state, and closed_by_diff describe whether it is still observed, so a settled finding can still be open there. settlement_basis is one of person_ruled, instrument_void, closed_by_diff, or unsettled. settled_by identifies the person for the first two bases when the ledger has an identity; otherwise it is null and settled_by_basis explains why. settled_at is the instant the ledger stamped a person_ruled or instrument_void ruling, written in the same transaction as the verdict. It is never updated_at, which records the row’s last write of any kind. A legacy row ruled before disposition timestamps existed returns settled_at: null with a basis explaining that age; closed_by_diff and unsettled also return null with their own basis because neither is a person ruling. With view=index, each entry carries only the join coordinates (finding_key, comparison, object_name, column_name), event_role, the family (family_signature, family_direction), and disposition_at_run. Index entries carry the same settlement block as row entries. disposition_at_run is the finding’s disposition when this comparison ran, not its current ledger disposition. The response also includes dispositions_at_run_available: true only when a lifecycle record exists and covers every non-closed finding measured by the run. A false value means the historical rulings cannot be fully reconstructed; treat each missing disposition_at_run as unknown rather than as an unreviewed finding. Closure rows are excluded from this coverage because they were not part of the run’s residue. evidence is present only on a finding this comparison closed — a closure is absent from the comparison’s own measured elements by construction, so nothing else describes it. The index is never sliced (offset is 0, limit is null): a client joins every measured element against it, and a truncated join does not fail, it silently blanks the affordances that hang off a finding key. With view=rows, the page also carries person_acceptance, the reviewed outcome without its item list, or null when no person’s acceptance stands. It includes the same approver_names map described in the comparison review response. Each row carries person_accepted: true only when the person accepted that finding and the comparison reads Verified; when person_acceptance is null, all rows are false. This is separate from the machine lifecycle: the finding remains observed and can still have state: "still_open". The index and keys views carry neither field. The row view’s acceptance check costs one review-log field-patches query per page. If a verified person’s exception stands, it also folds the review outcome and resolves one population row. When that outcome stands, this includes an uncapped stimulus run-feed scan, a scoped finding-ledger fold, the population’s complete member set, and the comparison’s findings ledger. With view=keys, each entry carries only finding_key, event_role, object_name, column_name, and last_seen_diff. Use it to enumerate findings for disposition without transferring or decoding their evidence and lifecycle trail; page it with offset and limit like view=rows. settlement_basis applies to every view before the page is cut, so a filtered page is a subset of the comparison, not merely a filter over one page; settlement_counts remains the whole-comparison denominator. Returns an empty page (total: 0) when the diff hash was never recorded — a comparison that exhibited nothing and one that was never run are the same absence to a reviewer, so neither is an error.

GET /api/estate/findings/families

Each row summarizes one systemic family: family_signature, member and object counts, and sample members. signature_shape is persisted only when every member was placed from its stored family coordinates; it is computed when any member had to be derived from its evidence document. Each sample member includes finding_key, object_name, column_name, and disposition, plus an evidence object containing only source_value and target_value; when evidence is absent, evidence is null. The listing omits member_keys and does not inline whole evidence documents. Use the family member or witness drill-down endpoints when you need the full finding evidence. Open families are returned first, largest first. The response also includes deferred_family_signatures: null unless with_deferrals=true, otherwise a list of the listed families the current person has parked with a Leave for now note. A family remains parked when later notes are added; the deferral is not replaced by the newest ordinary note. A deferral note is a family note whose body starts with [deferred:indeterminate]. Pair-scoped notes apply to that pair; an unscoped family note applies to the family across pairs. Only findings on comparisons the caller may see are folded into these rows; hidden findings do not contribute members, counts, or samples, and families with no visible members are omitted. A deferral badge counts only notes the caller may read: pair-scoped deferrals on visible pairs, plus pairless deferrals authored by the caller.

POST /api/estate/findings/{finding_key}/disposition

Successful responses include finding_key and disposition. The finding stays open — only re-verification on a later diff run (when the difference is gone) closes it. The adjudicator identity is taken from the authenticated session when auth is enabled, never from the request body; when auth is disabled, writes are attributed to local operator (auth disabled).

POST /api/estate/findings/families/{family_signature}/disposition

Successful responses include family_signature, disposition, applied_count (number of open member findings updated), and skipped_count (number of open members left alone because they already carried a diagnosis). Members stay open — only re-verification on a later diff run closes each one individually. The adjudicator identity is taken from the authenticated session when auth is enabled, never from the request body; when auth is disabled, writes are attributed to local operator (auth disabled).

POST /api/estate/comparison-events/{diff_hash}/disposition-packages

A package containing target_defect is refused before staging, whether the diagnosis appears in items, a family selector, or a corrective package. A send-back keeps its finding open until a fresh comparison verifies the fix; use the direct disposition_system_diff_finding tool instead. Other diagnoses that settle a difference without a fix remain in the human-approval package path. Staging also refuses the whole package before writing when an included lasting-rule promotion fails the approval preflight. The response’s refused_items identifies each affected finding_key, its locus, approval reason, and plain_reason; no person is asked to approve a package containing a promotion that cannot be licensed. The tool reference describes the corrective choices returned to the proposer; see the disposition package tool guidance. The package is validated against the addressed comparison’s current ledger state and accepts only findings most recently exhibited by that comparison. In initial mode, every item must be undiagnosed. In corrective mode, every item must already carry a diagnosis and the proposed diagnosis must differ from the one recorded; use this mode to replace a mistaken verdict. A selector is expanded inside the same transaction that stages the package, so its expansion response block reports the family and selected count. corrective with select is refused because a family selector names undiagnosed findings. Each item’s plain_summary is the one-sentence, business-language explanation shown to the person signing; note remains the technical rationale for the record. The waived disposition is a time-limited exception: its owner and future expiry are staged with the item and included in the approval projection. Terms on another disposition are refused. A retry that changes only waiver terms is a different request, not an idempotent replay. The latest Adjudicator verdict also constrains defect proposals: if it is question or spec_ambiguity, a proposed target_defect or spec_defect is refused with 400 unless that item cites a clarification_id recorded on an estate that scanned either side of this comparison, after the seat asked, and ratified by a signed-in person. An agent-certified clarification or one ratified with authentication disabled does not carry the defect. A promote_delta or waived item may instead cite the owner’s ruling: the clarification must be recorded on an estate that scanned either side of this comparison and must not be overturned; ratification is not required. Its provenance label is retained. Unanswered escalations are refused; the error names the seat’s outcome and provisional_position, explains why a cited clarification did not qualify when applicable, lists up to 20 affected findings, and gives a count for any remaining ones. A citation that fails these checks or appears on another item is refused; nothing is staged. Unknown fields at the request, selector, or item level are refused with 422; the error names the unknown and accepted fields, and nothing is staged. The response includes package_id, diff_hash, mode, item_count, subject_fingerprint, approval_url, approval_generation, action_review, and coverage; selector requests also include expansion, while listed requests return it as null. coverage records the comparison-wide exhibited, dispositioned, and undispositioned counts measured when the package was staged, plus proposed_here; retries return that original measurement. When a staged item proposes a different disposition from a still-open package on the same estate pair, the response also includes open_package_conflicts, with one entry per package (package_id, diff_hash, and item_count for the new package’s disagreeing items). The conflict is disclosed but does not refuse staging; whichever package is signed first sets the record. No finding is changed by this request. See propose_finding_disposition_package and propose_finding_disposition_correction for the agent hand-off guidance. Staging is agent-reachable and requires view_connections. With authentication enabled, the proposer identity is recorded for review; this request does not require a human approver. The approval link opens the package in Human Review. Packages staged through a Genesis agent tool use the acting agent as the proposer of record and retain the authenticated caller as the person the agent worked for; if the agent runtime has no bot id, the proposer is agent (unattributed). A direct HTTP request omits that agent context and remains attributed to its authenticated caller. When that caller is a service identity, staging also asks for Action Review approval immediately. If the request cannot be minted, the package remains staged and its read exposes can_request_approval so approval can be requested from the package later. If a person declines the package’s linked Action Review request, the package is closed as retracted and the comparison is free for the agent to stage a new initial package. Policy denial of a linked direct selective decision also closes that package generation; package reads identify it with retracted_as: "policy_denied_in_action_review".

GET /api/estate/comparison-events/{diff_hash}/disposition-packages

Returns data as summary rows, newest proposal first. Each row includes package_id, diff_hash, title, status, mode, proposer attribution, justification, proposed_at, item_count, decided_count, and undecided_count, approval_generation, and the package’s approval_url. decided_count counts all settled items, including legacy send-backs applied without a person’s approval and items returned to their proposer because no one could approve them; it is not necessarily the number of human rulings or signatures. Such items do not appear in the package’s human signature record. Rows also include retracted_at, retracted_by, and retracted_as; these are null while the package stands. For a package closed by an Action Review decline, retracted_as is declined_in_action_review; policy denial of a direct selective decision is policy_denied_in_action_review. Summary rows also include can_request_approval, true for an open agent- or service-staged package with no current Action Review owner when no request has been made or its prior request can be renewed. Use this state to offer the package’s approval-request action. Each row also includes approval_run, the latest approval run for the current package generation, or null when there is none to show. It reports whether an approval is accepted, applying, done, or failed, and its signed-item count. A failed run remains in listing rows for one hour; the package read retains the current generation’s latest run. Evidence-bound Prover packages also expose evidence_error; it is null for legacy packages and valid evidence, and contains the refusal reason when persisted evidence cannot be authenticated. viewer_can_sign is true when the caller has a signing identity and the package is open; it is false with viewer_cannot_sign_reason set to no_signing_identity or not_open otherwise. Any person may sign, including the package proposer, and no special approval permission is required. viewer_can_apply matches viewer_can_sign. These verdicts are never null. Each row also includes viewer_signs_as: the identity this caller’s signature would carry, resolved by the same rule as the approval write, or null when the caller has no signing identity. Listing a package does not include its items or evidence — that would cost a ledger join per row — and each row names where they are instead: items_at is the path of the package read endpoint for that package, composed from the route this API registers for it. GET /api/estate/disposition-packages/mine rows carry the same items_at.

GET /api/estate/disposition-packages/mine

Returns data as the caller’s most recent standing package sign-offs across comparisons. Each row includes the package summary fields plus my_decided_count, my_approved_count, my_rejected_count, my_decided_keys, and my_last_decided_at; only that caller’s decisions are counted. Decisions later withdrawn are not listed, although they remain in the audit history. The response also includes truncated; when true, older signatures may exist beyond the bounded scan of recent decisions and the client must surface that the worklist is partial. A caller without a signing identity receives an empty data array. With authentication disabled and no X-Genesis-Signed-As claim, the listing treats the system_default_user and local-operator principals as the same signer, so Action Review package approvals recorded for the local operator are included.

GET /api/estate/signoffs/waiting

Returns data as a map keyed by each requested stored comparison this caller may see whose snapshots resolve to an estate pair. A comparison outside the caller’s visible scope, not stored, or without a resolvable pair is absent rather than returned with empty lists. Each value has packages, the comparison’s open disposition packages, and rules, its rule-review rows; either list can be empty when no queue items are waiting. Both lists carry the caller’s signability fields. Package rows do not include Action Review links; their action_review is null and can_request_approval is false. Use the comparison’s package listing when Action Review links are needed. The rule rows include bundle-hold and signability details, as in the single-comparison rule-review read. This read is for the Prover landing’s Needs you worklist. It does not run rule consent read-repair. It requires view_connections and is read-only. More than 200 distinct hashes returns 400 rather than silently truncating the worklist. Rule rows are selected per comparison pair: a rule appears when no binding exists for that exact pair and its earliest definition is not retired. A binding to another pair does not clear this pair’s work item, and legacy definition status does not hide an otherwise unbound rule.

GET /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}

The response includes data with the package status (open, closed, or retracted) and mode (initial or corrective), the verified proposer identity, any unverified proposer_display_name claim, justification, item_count, decided_count and undecided_count (the same counts the listing row carries), and each item’s proposed disposition, plain_summary, optional rule_id, promotes_rule, decision, reviewer identity and timestamp, stale state, and live subject evidence. Each item also returns its staged waiver_owner and waiver_expires_at when terms were recorded. returned_to_proposer is null unless the item was handed back because no approvable lasting rule remained; then it contains the approval’s reason, its plain_reason, and returned_at. This is settled package state, not a human decision. Subject evidence includes comparison, object name and resolved object_type when known, column name, status, last-seen comparison, current disposition, source and target values, detail, and family coordinates. An item whose defect verdict was carried over the seat’s question, or a promote_delta or waived item with a cited owner’s ruling, also includes clarification with clarification_id, subject, statement, label, reviewed_by, reviewed_at, and answers_outcome. reviewed_by and reviewed_at may be null for a ruling nobody reviewed; answers_outcome is null on a ruling item because it answers no seat question. The field is null when the item cites no clarification. For corrective packages, each item also exposes current_disposition, the diagnosis recorded when the package was staged, so the signing surface shows the verdict being replaced as well as the proposed one. Each item carries adjudicator_verdict, the Adjudicator ruling that backs it: verdict_id, adjudication_id, outcome, confidence, the finding’s durable finding_id (#N, or null), and link — staged when the ruling’s own proceeding staged this package, ledger when it is the newest ruling the verdict log holds on the finding. It is null when no proceeding ruled on the finding. It also carries departs_from — the earlier ruling (verdict_id, outcome) this verdict overturned, or null. Reading it costs one or two verdict-log queries per package read. Each item carries open_package_conflicts: every package that was still open on the same estate pair when this one was staged and whose item on the same finding proposed a different disposition (package_id, diff_hash, proposed_disposition); empty when none. Staging is not refused over it — whichever package is signed first sets the record. A moved item is marked stale; fresh undecided items remain independently reviewable. signature lists every signer separately, the items each signer decided, and their approval/rejection counts; it is null before any decision. A decision records self_approved: true when its signer is also the package proposer. viewer_signs_as identifies the identity that would be recorded if the caller signs, or is null when the caller may read but may not sign. A person’s own PAT is accepted and labelled via personal access token. The response also includes viewer_can_sign and viewer_can_apply, plus viewer_cannot_sign_reason when the caller cannot sign. The reason is no_signing_identity or not_open; it is null when the caller can sign. Any signed-in person, including the proposer, can sign without a special approval permission, so viewer_can_apply matches viewer_can_sign. viewer_withdrawable_keys is a string[] (default []) of item keys whose signatures this caller may withdraw. It is empty unless the caller has a verified principal, the package is not corrective, and the listed rulings were recorded under that principal. A signature whose lasting rule still licenses the pair or a reclaim conflict can still be refused when withdrawal is submitted. Evidence-bound Prover packages additionally return evidence_fingerprint, evidence_summary (the captured comparison, scope, and member count), evidence_scope, evidence_member_count, and remeasurement: "not_enqueued". evidence_error is null when the persisted envelope authenticates and contains the server’s refusal reason when it does not; an evidence error does not change the separate viewer_signs_as eligibility result. The response also includes approval_generation, the package generation an Action Review item must name to own approval, and action_review, the current Action Review item head when one owns this generation. A lapsed or applied item may be returned as action_review_history; it is history, not the current owner. It also includes can_request_approval, true for an open package staged by an agent or service when no Action Review item currently owns approval and the request has not yet been made or can be renewed. A service-staged HTTP package normally attempts to create its request during staging; this field surfaces the retry path when no request was created or its request later lapsed. It also returns retracted_at, retracted_by, and retracted_as, which are null while the package stands; retracted_as: "declined_in_action_review" identifies a package closed by a person’s Action Review decline, and "policy_denied_in_action_review" identifies one closed by policy denial. The response also includes approval_projection_digest, the digest that a native selective decision must send with the package generation. It also includes promotion_previews, the staged promotions’ shared pair_fingerprint, previews, and unavailable entries (including each unavailable item’s plain_reason). The preview contents and refusal fields are the same as the promotion-preview endpoint. Because the approval projection already computes these previews, the package read returns them with the package rather than requiring a second request from the approval page. approval_run reports the current generation’s latest approval: run_id, state (accepted, applying, done, or failed), started_by, timestamps, the optional action_review_proposal_id, total and signed counts, the named items and their signed flags, and signed_by. A failed run includes reason, not_recorded entries with per-item retryability, and the overall retryable flag. The run reflects recorded package decisions, not just the submitted request; done means every named item carries its requested ruling.

GET /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/promotion-preview

Preview the correspondence rules that approving this package’s promotion items would create. The response is a snapshot and writes nothing; the package may change before approval, which remains authoritative. This read costs a package fold, a findings-ledger read, and rule resolution and pair-binding reads for the promotions previewed, so call it once per package rather than once per item. data contains diff_hash, package_id, the shared pair_fingerprint, previews, and unavailable. Each preview describes the drafted rule, including its rule_id, component_pair, rule_kind, title, claim, applies_to, and edge_fixtures. reusability_refusal is the agent-facing refusal or null; plain_reusability_refusal explains the same refusal in business English or is null when there is no such refusal. A revoked approval for this estate pair is reported here before the reviewer approves. Each unavailable entry includes finding_key, reason, and plain_reason; the latter explains why the promotion cannot be approved and what the reviewer can do. Items approval would refuse are reported here rather than shown as licences. The preview also applies the approval’s field-survival check using the pair’s configuration. Qualification that requires running against the configured pair remains checked during approval.

GET /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/record

Read the package’s signing record and the Action Review request associated with its current generation, without loading the package’s per-item review data. The approval result line can use this endpoint instead of the full package read. The response returns data.package_id, data.diff_hash, data.title, data.signature, data.action_review, and data.action_review_history. signature contains each signer’s identity and the decisions covered by that signature; it is null before any decision. action_review is the request that currently owns the package approval, or null when there is no owner. action_review_history is the request for the current package generation when it no longer owns approval (for example, after applying or lapsing undecided); it is not the current owner. The request objects use the same projection as the full disposition package read. This read does not return package items, evidence, staleness, the viewer’s verdict, or approval projection. Each call performs a view_connections permission check, one comparison-existence read, one read of this package’s review-log events, and one Action Review link read; it does not do the full read’s findings-ledger join, Adjudicator verdict read, or approval projection.

POST /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/approve

For packages otherwise eligible for direct selective decisions, this path is available only after their all-staged Action Review request lapses. Agent- or service-staged packages cannot be approved or partially decided here. A person can decline one only by rejecting every open item in a single submission when no live Action Review request covers it. Decide a live request through Action Review. After a request lapses, a whole-package decline needs no renewal; approval still does. Each native selective submission is recorded as Action Review action prover.decide_disposition_package.v1; its response includes the sealed action_review_proposal_id. The digest and generation bind the request to the package projection the reviewer saw and are checked again under the package lock. Only named items are ruled on; omitted items remain undecided. approve writes the proposed diagnosis, or the supplied amended disposition, under the authenticated human’s identity. For each approved item, a disposition other than target_defect can also resolve matching unexpected_change items in the selected Human Review population. Duplicate-coordinate population groups resolve only when the package covers every member; target_defect does not license a population item. reject retires the proposal without writing a disposition. The response includes applied_count, skipped_count, rejected_count, applied_keys, rejected_keys, approved_by, proposed_by, population_resolved_count, the number of Human Review blockers resolved by this approval, ratified_rules (each with finding_key and rule_id), corrections (each with finding_key, prior_disposition, and the new disposition), and refused_items (each with finding_key and the server-stated reason). An eligible promote_pi item qualifies and ratifies its correspondence rule in the same signing action, using the reviewed comparison’s persisted pair configuration. A rule proposed by an agent or service cannot be ratified as part of a person’s package: request its own Action Review approval first, then approve the package. If a promotion cannot be licensed, that item remains undecided and is reported in refused_items; other selected items can still be applied. A package decision is selective, and an item cannot be decided a second time. For a corrective package, approval supersedes the staged prior diagnosis using a compare-and-swap check; the response’s corrections list records each old-to-new transition. If a selected promotion is refused, other selected items can still be applied; refused items remain undecided and the response has complete: false. complete is true only when the submission succeeds without refused items. If every selected approval is refused, no ruling is recorded. When the remaining undecided items also all fail the approval preflight, the response includes returned_keys for the items handed back to the proposer; the package closes instead of asking another person to review rules that cannot be approved. For an initial package, approval writes findings with set-based statements rather than one write per item. Each row carries its own diagnosis, note, waiver terms, and compare-and-swap values, so distinct item values do not require separate writes. Typical packages use three SQL statements; larger batches are split to stay within bound-value limits. The endpoint responds after the Prover decision commits. For an applied decision, family-dossier cache invalidation, sealing the linked Action Review outcome, and any instrument_defect Prover-defect filing finish after the response. Until the outcome is sealed, the linked Action Review request remains claimed. Filing status is recorded on each finding and can be read from the findings dispositions API; see False alarm (Prover) for the filing-status meanings. Any signed-in person can approve this package, including its proposer, without a special approval permission. The response and stored decision identify an approval by the proposer with self_approved: true; a person’s PAT signs as its owner. Approval checks the staged subject state transactionally; a changed finding cannot be approved, although it may still be rejected so a stale package can be retired. For an evidence-bound Prover package whose evidence_error is non-null, approval is refused with the same stale/refusal response; rejection remains available so the unsafe package can be retired. The package does not enqueue a fresh comparison or remeasurement.

POST /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/approval-runs

Accepts the same selective decision request as /approve, but answers once the approval run is recorded rather than waiting for its decision to finish. The path parameters and body are the same as /approve. The response uses HTTP 202 and includes run plus already_signed_keys. The package read and package-list rows expose the run while it is being recorded. If every named item already carries exactly the requested ruling, run is null, no new run is written, and already_signed_keys names those items. run.state is accepted before recording begins, applying while the decision is being applied, done once all named items carry the requested rulings, or failed if the run stops with items not recorded. The run includes its run_id, signer and timestamps, total and signed counts, per-item signed flags, signed_by, and the linked action_review_proposal_id when known. A failed run also returns a plain reason, not_recorded entries with individual retryable flags, and an overall retryable flag. Use those fields to show progress and determine whether an item can safely be submitted again. If an attempted decision has an uncertain outcome, the failed run’s reason says the server could not confirm whether it was recorded and includes the request ID; the underlying detail is in the server log, not the response. Reload the package before trying again to check where its decisions stand. Any signed-in person can start an approval run, including the package proposer; no special approval permission is required. A self-approval is recorded as self_approved: true. It refuses an approval already in progress for the same package. Other decision checks match /approve, including the package generation and projection guards, Action Review ownership, and waiver expiry validation.

POST /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/renew-approval

Mints a fresh Action Review item for the same open package only when the previous request lapsed without a decision. The response includes proposal_id, its effective status, and renewed; a live item is reused with renewed: false. The generation is a compare-and-swap guard: if the package changed after the page read, the request returns 409 with package_changed instead of asking a person to approve an unread package. A declined request, an applied request, or a package that is no longer open cannot be renewed.

POST /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/unsign

This is a compensating action, not a delete: the decision remains in the review history as signed and then retracted, while successfully retracted findings are returned to the open undiagnosed set. The caller must be the same person identity whose signature covers at least one named item. With authentication enabled, that can be the person’s sign-in or their own PAT; an agent or machine token cannot withdraw a signature. Items whose ledger row no longer carries that signature are left unchanged and disclosed in skipped; a request naming only another person’s items is rejected. If the signature ratified a correspondence rule that is still licensing the comparison’s estate pair, retraction is refused; revoke that rule first in the Platform rules surface, then retry this endpoint. Successful responses include package_id, retracted_count, retracted_keys, skipped (each with finding_key and the server-stated reason), retracted_by, and population_reopened_count, the number of Human Review items whose package-based resolution was re-opened. A corrective package cannot be unsigned: clearing it would erase the diagnosis it superseded rather than restore the prior verdict and attribution. Submit another corrective package to change that verdict. Retracting an approval re-opens its package-bound population items in the same transaction; items still covered by the package’s remaining approvals, or resolved by another authorization, remain resolved. The signature projection in the package read shrinks to exclude items that were retracted.

POST /api/estate/comparison-events/{diff_hash}/disposition-packages/{package_id}/retract

Withdraws the caller’s own package before any item has a decision. Any signed-in person may also withdraw an unanswered package staged by an agent or service; this does not permit withdrawing a package staged by another person. The person’s own PAT is accepted as their identity. The operation changes no finding disposition; it records the package as retracted and removes it from the reviewer’s waiting worklist. The response includes package_id, diff_hash, status: "retracted", retracted_by, retracted_as ("proposer" or "operator"), reason, and item_count. A package closed because its linked Action Review request was declined or policy denied is instead represented in package reads with retracted_as: "declined_in_action_review" or "policy_denied_in_action_review"; it releases its open-package claim so a new initial package can be staged. The caller’s identity is resolved server-side; there is no request body identity field. When supplied, reason is trimmed and recorded; blank reasons are stored as absent.

GET /api/estate/comparisons/{diff_hash}/review/bundles

Returns data with diff_hash and bundles[], oldest first. Each bundle includes bundle_id, the display-only proposed_by, proposer_principal (the proposal principal; with authentication disabled, a local operator’s proposal uses local-operator), proposed_at, rationale, brief when the bundle carries one, run_id, population_hash, carries, approval_route, approval_url, approval_url_basis, and approval_path. approval_route is action_review for an agent-staged bundle carrying correspondence rules and no disposition or evidence-gap packages; it is direct otherwise. approval_path is the same review page relative to the Genesis dashboard and remains present when approval_url is null because the deployment publishes no public base URL. A bundle carrying disposition packages or using the action_review route also includes action_review, with the linked proposal’s proposal_id, status, and url. If that link cannot be read, status is unavailable, reason is ledger_read_failed or adapter_unavailable, and proposal_id and url are null. It also includes viewer_signs_as, the signer label this caller’s approval would record, viewer_can_sign, and viewer_cannot_sign_reason. The latter two reflect whether the approval door would accept this caller on this bundle: viewer_can_sign is false with reason package_requires_action_review for bundles carrying disposition packages or agent_bundle_requires_action_review for rules-only bundles using the action_review route; neither kind can be approved through the bundle endpoint. Otherwise, viewer_can_sign is null when no signer is available, false when approval would be refused, and true when it would be accepted. When the signer is unavailable, viewer_cannot_sign_reason is no_signing_identity; otherwise it is not_open when the bundle is no longer awaiting approval. The proposer may sign their own bundle without a special permission. It is null when the caller may sign. The reason reports the first refusal the approval door would encounter. population_moved is true when a bundle carrying evidence-gap packages was assembled against a different current population hash; current_population_hash is null for a bundle that carries no evidence-gap packages. Bundles whose members were already settled or whose recorded scope changed are not offered as standing work. When evidence gaps are carried, carries.evidence_gap_packages also returns the staged waiver_owner and waiver_expires_at.

POST /api/estate/comparisons/{diff_hash}/review/bundles

Unknown body fields are refused with 422; the error names the unknown and accepted fields, and no bundle is staged. A part that exceeds its brief bound is also refused; the server does not shorten it. The response includes bundle_id, brief when the bundle carries one, approval_route, the comparison’s current run_id and population_hash, the trimmed rationale, approval_url and its basis, approval_path, and carries. For a bundle carrying disposition packages, it also includes action_review with the composite proposal’s proposal_id, status, url, card_sent result, and adopted_package_requests, the array of package-request proposal IDs withdrawn in favor of the composite request. This array is empty for bundles proposed through this human-facing HTTP route. Use action_review.url for that human decision; the separate bundle approval endpoint cannot approve package members. An agent-staged bundle carrying correspondence rules and no package members also uses route action_review and includes that linked request. Proposals staged by a human through this HTTP endpoint use route direct. For direct-route bundles, use approval_url (or approval_path when the URL is null). approval_path is the same review page relative to the Genesis dashboard and remains present when approval_url is null because the deployment publishes no public base URL. The carries object reports each kind’s ids and count: correspondence rules as count of of_proposed, disposition packages with submitted item_count of of_undispositioned_findings, and evidence-gap packages with member counts of of_open_members. It also includes excluded_rules; each excluded rule has its rule_id, title, rule_kind, the eligibility refusal, and the remedy. A human’s all_proposed_rules selection leaves out a rule whose sign_off_route is action_review; its remedy is to request approval for that rule separately. Data-derived and unclassified rules are also left out of bulk selection. A person cannot name one in this route; an agent may name one in a rules-only bundle through propose_approval_bundle, subject to its one-rule limit. Repeating the same membership and recorded scope for the same proposer replays the standing bundle without writing another event. Staging changes no rule, package, finding, or evidence-gap decision. A bundle is bound to the scope read when it is proposed; approval refuses if a package’s undecided items or an evidence-gap package’s members moved after staging. When the bundle creates a composite Action Review request, it can claim each correspondence rule it carries only if no other live request or open agent- or service-staged package owns that rule on the estate pair. A package owns a rule it proposed or that one of its items promotes under; a conflict returns rule_owned_by_package with the owning package identifier. Replacements and retractions clear pending bundle asks from Action Review.

POST /api/estate/comparisons/{diff_hash}/review/bundles/{bundle_id}/approve

The request has no body: approval cannot widen the membership fixed at proposal time. A bundle carrying any disposition package is refused atomically with rule: "package_requires_action_review"; an agent-staged rules-only bundle is refused with rule: "agent_bundle_requires_action_review". Neither refusal approves any member; both bundles settle through their composite Action Review request. For other bundles, one transaction sends correspondence rules through rule ratification and evidence-gap members through the exception-approval path. The response includes bundle_id, the approval event id, approved_by, and per-kind results for rules, packages, and evidence_gaps. Any signed-in person can approve this bundle, including its proposer, without a special approval permission. The response includes self_approved to identify an approval by the bundle’s proposer. Bundles carrying disposition packages are refused through this endpoint as described above. With authentication enabled, a person’s own sign-in or PAT can sign and a typed name is refused; PAT signatures are attributed to their owner. With authentication disabled, the caller is recorded under X-Genesis-Signed-As with (self-reported, auth disabled), or as local operator (auth disabled) when the header is absent.

GET /api/estate/remediation-requests

Returns data as an array of defect remediation requests, newest first. Only requests whose named finding and/or estate pair the caller may see are returned; unattributable or hidden requests are omitted and not counted. Each row includes lifecycle fields such as status, created_by (filer), and dispatched_by (the human who authorised dispatch, set when status is dispatched; null while still open). Filing the same defect twice is idempotent — the server content-addresses the request.

POST /api/estate/remediation-requests

Successful responses include created (false when the request already existed) and data with the persisted remediation request. The server generates authoritative fix instructions — they are not trusted from the client. Dispositioning the finding or family as target_defect remains a separate call (POST /api/estate/findings/.../disposition). The filer identity is taken from the authenticated session when auth is enabled, never from the request body; when auth is disabled, writes are attributed to local operator (auth disabled).

POST /api/estate/remediation-requests/{request_id}/dispatch

Successful responses include request_id and status (dispatched). The dispatch transition is human-gated: the authenticated adjudicator is stamped on the persisted request as dispatched_by (readable via GET /api/estate/remediation-requests) when auth is enabled; when auth is disabled, writes are attributed to local operator (auth disabled). That field is written once at dispatch and is not cleared by later lifecycle transitions.

GET /api/estate/notes

Returns data as an array of collaboration notes on one target, oldest first. Notes on an estate pair are returned only when the caller may see a comparison on that pair. Pairless notes are returned only when the caller authored them.

POST /api/estate/notes

Successful responses include data with the persisted note. Notes are append-only. The author identity is taken from the authenticated session when auth is enabled, never from the request body; when auth is disabled, writes are attributed to local operator (auth disabled).

GET /api/estate/comparisons/{diff_hash}/history

List who touched one comparison and the conversation for each act, newest first. The history combines runs that name the comparison, recorded review decisions, rulings, comments and proposals, and the requirements documents those runs were graded against. Each entry has a plain-language action, an actor (person, agent, or unrecorded), and a kind (run, review, or requirements). A person actor can include their directory name; on_behalf_of identifies a person an agent acted for. run_id and finding_id are present when the entry refers to one. conversation.state is openable when the caller may open the recorded conversation, not_shared_with_you when it is recorded but not accessible to the caller, not_linkable when the thread is recorded without its agent, or not_recorded when there is no conversation address. Only an openable conversation includes bot_id and thread_id; the other states do not disclose its address. Returns data with diff_hash, entries[], and truncated. The response keeps at most the newest 200 entries. truncated is true when entries exceed that limit, the run or review-event read reaches its 200-run or 500-event limit, or more than 20 distinct requirements documents are named. The read checks comparison visibility, scans narrow run-table columns, reads at most 500 review events, and reads provenance for up to 20 requirements documents. It also checks access once per distinct conversation in the returned entries and batches display-name lookup for named people. Unknown and caller-invisible comparisons both return 404. Each entries[] object has these fields:

GET /api/estate/comparisons/{diff_hash}/latest-conversation

Find the newest conversation about a comparison that belongs to the caller. The Prover workspace uses this when a person clicks an Ask button, so the question continues in the conversation that contains the comparison’s context. Only the caller’s own conversation is returned; a public or shared conversation, or one the caller can read through an admin permission, is not eligible. The route requires VIEW_CONNECTIONS. If no owned conversation is found, conversation is null and the Ask flow starts a new conversation. The lookup considers conversations recorded by runs about the comparison and by staged decision packages. It checks ownership newest first, up to five candidates. It reads at most ten run rows and ten review-event rows, then checks thread ownership until a match is found. Unknown and caller-invisible comparisons both return 404. Returns data with diff_hash and conversation:

GET /api/estate/comparisons/{diff_hash}/review

Returns data with the folded comparison review state: For the movement basis vocabulary and the distinction between a clean zero and an unmeasurable result, see the find_proofs response contract. Each item in findings includes finding_id, diagnosis, disposition, workflow, owner, exception_approved, exception_run_id, exception_population_hash, exception_package_id, updated_by, updated_at, comments (array of {comment_id, text, author, created_at}), and audit (array of {event_id, kind, author, created_at, detail}).

GET /api/estate/comparisons/{diff_hash}/attestation

The response returns data.diff_hash and data.attestation. The attestation is null when no stored attestation binds this comparison; otherwise it is the newest match, selected from attestations on contracts for the comparison’s source snapshot whose transition names this exact diff_hash. This summary costs two reads. It does not verify the signature or re-derive the attestation bundle; use POST /api/estate/attestations/{attestation_id}/verify for that check.

POST /api/estate/comparisons/{diff_hash}/proof-report

Returns the report bytes as application/pdf or text/markdown. The response sets Content-Disposition and X-Attestation-Id; fetch GET /api/estate/attestations/{attestation_id} to retrieve the signed envelope to ship beside the report. The first download mints the signed statement; later downloads reuse it while the underlying comparison, grade, specification, approval and signing key are unchanged. The report requires a standing Human Review approval, a verified grade for this comparison against its sealed specification with every check passing, no unexplained or undetermined differences after the specification’s reclassification, no open unexplained behavioural findings, and configured report signing. A stimulus run is not required: when behaviour was not tested, the report discloses that limitation prominently on its first page and in its limits. 409 returns payload.reasons when an eligibility condition is not met, or immediately when another report for the same comparison is being prepared; 404 means the comparison is unknown. PDF rendering can take up to two minutes. Cost: review approval and readiness reads (including an uncapped stimulus-run feed scan), signing-key resolution and, for a new statement, an attestation mint; PDF format also runs one headless Chromium render.

GET /api/estate/attestation/public-key

Returns data with the public verification material; the private signing key is never returned. Cost: one config read, one secret lookup and one permission check. Callers with manage_secrets also receive key-source diagnostics, which cost an environment scan, a config read and up to two secret-existence reads; other callers do not receive an admin field.

POST /api/estate/attestation/signing-key/test

No request body is required. This signs a throwaway statement with the configured key and verifies it with the public half. It writes nothing, creates no attestation, and is safe to repeat. The response’s data.ok reports whether the check passed, data.key_id contains the key id when available, and data.message explains the result and, when relevant, where the key comes from. Missing or invalid key material is returned in a 200 result rather than an HTTP error. Cost: resolving the key, one signature and one verification in memory, plus an environment scan, one config read and one secret-existence read for the key-source diagnostics. Requires manage_secrets.

POST /api/estate/attestation/signing-key

No request body is required. The endpoint generates an Ed25519 key pair on the server, stores the private key in the encrypted shared secret store, and returns only data.key_id, data.public_key (base64 raw bytes), and data.public_key_pem. The key id is derived from the public key unless an operator configured estate_char.attestation.key_id. This operation never replaces or rotates an existing key and refuses when the signing field is configured by an operator or environment override. Cost: key generation, secret-store creation and readback, and config and environment override checks.

GET /api/estate/comparisons/{diff_hash}/review/population

A comparison hidden from the caller returns 404, as an unknown comparison does. Successful responses include data with the selected conformance run’s population status and one member page: For a partial seal, spec_grade.open_clauses is the number of clauses left open and spec_grade.spec_seal_state is partial; an unreadable seal kind is unknown. Such a grade has verified: false and cannot stand in for a verified grade on a comparison with no paired tables. Each item in members includes finding_id, kind (unexpected_change, missing_requirement, or evidence_gap), comparison, object_name, column, spec_id, locus, description, detail, source_value, target_value, cause, related_finding_id, rename_hypothesis_basis, samples, and resolution. samples is empty except on a failed spec check (comparison obligation_failed), which has no single source or target value. It lists the keys the check’s detail sentence quotes, each with the same evidence the sentence gives it. Each entry has key, column, source, target, no_row_on and sampled. source and target are the values the check read, where null is SQL NULL and "" is empty text; a key that differed in several columns has one entry per column. no_row_on is source or target when the check placed every failing key on that side, and the entry then carries no values. sampled is false for a quoted key the check sampled no value for and placed on no side. unexpected_change members also include finding_key and witnesses_available. finding_key is the findings-ledger key accepted by the finding-scoped witness endpoint; it is populated only when exactly one non-closed ledger finding was minted at the member’s coordinate. It is null for requirement or evidence gaps, an unminted locus, or an ambiguous coordinate shared by multiple entity-scoped findings. witnesses_available is true when that key can be used to fetch a witness document and otherwise false; a witness document may still disclose that localization was declined. resolution is open, ruling, exception, or withheld: ruling means a person’s recorded ledger ruling accounts for the member, exception means an approved one-time exception accounts for it, withheld means the engine measured a reading from an undeclared Data Vault pairing and nobody needs to disposition it, and open means neither a person nor the engine accounts for it. When both a person decision and withholding could apply, the person decision remains. Withholding applies only to unexpected_change and evidence_gap members at the coordinates in withheld_readings; an unmet missing_requirement remains unresolved. When exactly one object_missing member and one object_surplus member share the same final dotted object-name segment, both are annotated with cause="rename_hypothesis", each member’s related_finding_id points to the other finding, and rename_hypothesis_basis is measured_agreement when their measured row count and boundaries agree or shared_object_name when only the final dotted name segment matched. Measurement agreement is considered before name matching, and ambiguous candidates are not linked. Name matching follows the comparison’s case_insensitive_object_names setting. For a partial seal, each open clause adds an evidence_gap member with cause: "open_clause" and object_name / locus set to clause <clause-id>. Each clause is independently addressable; it remains resolution: "open" until accounted for. Requested-change members also include failed_by, the ids of the spec checks that failed that item. It is empty when no check failed it and on every other member. This field distinguishes an item known to have failed from one no check decided. A failed spec-check member also includes relations_read: the graded-side tables it read, in record order, when no structural item claims the check (object_name is empty). It is empty on other members and when the check has a structural object. Use this list for the objects named by the check instead of parsing its display locus. A new target column nobody accounts for appears as an evidence_gap with cause: "target_column_unaccounted", its object.column in locus, and a description and detail explaining the person decision. It remains open until reviewed; an approved exception package records the columns as expected, while a column judged spurious must be sent back as a defect. See scope verdicts for how the grade identifies these columns. An input scope member also carries scope_target_presence: present or absent according to whether the target holds the object. It is null on every other member; unlike scope_status: "indeterminate", it distinguishes an input that is present from one that is absent. For a failed-check member, detail makes sampled evidence unambiguous: SQL NULL is shown as NULL, empty text as "" (empty text), and text that spells either marker is quoted and marked (text). A quoted key says (no row on the source) or (no row on the target) when its missing side was established; different keys can identify different missing sides. If no value was sampled and no side was established, it says (no value sampled). Numeric-looking text with leading zeroes, such as 0110, keeps its digits rather than being read as a number. resolution may also be false_alarm for a member settled by an instrument_defect ruling; those members are counted in resolved_as_false_alarm_count rather than resolved_by_ruling_count. See False alarm (Prover) for the user-facing meaning. Use this route (not a truncated findings sample) to page the full worklist and to obtain the run_id / population_hash pair callers should send with approval writes.

GET /api/estate/grades/{run_id}/review/population

The response has the same member-page fields as the comparison population route, but its members are the grade’s failed checks and its review is addressed by run_id, not a comparison hash. comparison_made is false; proofless_grade identifies the spec and seal that were graded, displays the disclosure that no comparison against a build was made, and includes the next call that can create one. approval_refusal is always present and explains that the review can take rulings but cannot carry an approval. resolved_by_exception_count counts failed checks covered by approved one-time exceptions. The route returns 404 when the run is not a grade that read no comparison or when the caller may not see the run.

POST /api/estate/grades/{run_id}/review/finding

At least one review-state field must be present. The route applies the same patch semantics as the comparison finding route and returns the refreshed proofless review page, including approval_refusal. Granting exception_approved: true requires a genuinely authenticated human on an auth-enabled stack. A stale expected grade or population returns 409; a non-proofless or hidden run returns the same 404 as the proofless review-page read.

GET /api/estate/comparisons/{diff_hash}/review/evidence-gap-packages

The response includes data with the currently offered evidence-gap exception packages and their population binding: Each item in packages includes package_id, object_name, cause, measured_columns, member_finding_ids, members, member_count, open_count, approved_count, package_approved_count, and approved_by. Evidence gaps withheld because the comparison paired a legacy object with an undeclared Data Vault object are not offered as exception packages; read the population response’s undeclared_target_shape and withheld_readings instead. Each item in members includes finding_id, comparison, column, locus, detail, and resolution. measured_columns identifies where the abstaining measurements sit; it is not a claim that those columns caused a sibling’s abstention. approved_count includes members accounted for by any exception on the population, while package_approved_count counts only members signed by this package. approved_by is populated only when this package’s signature covers every member. An evidence-gap package is offered only when the comparison has a finished run and a complete stored population. Members without a recorded machine cause are not packaged and remain available for per-finding review. Package ids are tied to the run, population hash, object, and cause, so a stale id from a superseded population is not reusable.

POST /api/estate/comparisons/{diff_hash}/review/evidence-gap-packages/{package_id}/approve

The request has no body. The server accepts every open member currently covered by the package, binds each approval to the package’s run_id and population_hash, and records the authenticated human as approved_by. Members already accounted for by any exception on that population are skipped; the package does not create a ruling, disposition, or correspondence rule. The response includes data with package_id, diff_hash, approved_count (members changed by this call), unresolved_count (members still blocking sign-off), and approved_by. Granting the package requires the same genuinely authenticated-human check as a per-finding exception approval. On an auth-enabled stack, system, machine, and delegated identities receive 403; with auth disabled, the approval is permitted and attributed locally.

POST /api/estate/comparisons/{diff_hash}/review/evidence-gap-packages/{package_id}/withdraw

The request has no body. The server withdraws only the members approved by this package, reopening them for review. Hand-approved members and members approved by another package remain accounted for. Any permitted reviewer or service identity may withdraw the package; this route does not require the original signer or the genuinely authenticated-human approval gate. The response includes data with package_id, diff_hash, and reopened_count (members reopened by this call). Re-read the review population after withdrawal to refresh the worklist and sign-off counts.

GET /api/estate/comparisons/{diff_hash}/witnesses

Use the comparison, object, and column values from one review-population member. The response is the same bounded witness document served by GET /api/estate/findings/{finding_key}/witnesses: per-side column definitions and rows, counts, alignment caveats, and the full plan disclosure. Exact, sampled, and declined localization semantics are preserved. This route is useful when finding_key is unavailable; only findings on comparisons the caller may see are considered, and it refuses to guess when multiple visible entity-scoped findings share the coordinate. A hidden comparison is answered as not found. For a comparison with a verified caller-declared row identity, witness localization pairs rows under that declared key. Otherwise it uses the comparison’s disclosed basis, if any, and preserves the existing declined or sampled behavior when localization cannot be proved.

POST /api/estate/comparisons/{diff_hash}/review/finding

At least one of diagnosis, disposition, workflow, owner, or exception_approved must be present in the request body. Patch semantics: only fields explicitly set in the body are applied, so two reviewers editing different fields of one finding merge instead of clobbering each other. Writes to the same field are last-write-wins; superseded values remain in the finding’s audit trail. Changing diagnosis or disposition retracts exception_approved and resets the run-level decision to awaiting_review. When exception_approved is true and both run_id and population_hash are present, the server records exception_run_id and exception_population_hash on the finding so a later re-grade cannot silently reuse the exception. When a disposition package approval resolves the exception, the response also records the package id in exception_package_id; this field is server-written and is null for a hand-approved exception. Granting a one-time exception (exception_approved: true) additionally requires a genuinely authenticated human: on a stack with auth enabled a system, machine or delegated identity is refused with 403. The annotation fields — and clearing an exception — are not gated that way. With auth disabled the approval is permitted and attributed locally, because every caller on such a stack already reads as the administrator. Successful responses include data with the whole folded comparison review state (not just the edited finding), so a reviewer sees concurrent writes the moment they save. The author is taken from the authenticated request context (email, then display name, then user id), never from the body; when auth is disabled or no identity is available, writes are attributed to local operator (auth disabled).

POST /api/estate/comparisons/{diff_hash}/review/findings/bulk

The request applies one explicitly-set review patch and optional shared comment to every named finding in a single transaction. It is all-or-nothing: blank or duplicate ids, more than 200 ids, ids over the length limit, or a request that sets neither a review field nor a comment is refused before any event is written. Granting an exception carries the SAME human-authority requirement as the single-finding route, so a caller refused there cannot approve by batching. For a group exception (exception_approved: true) without an explicit owner, the authenticated reviewer is recorded as the owner of each named finding that has no owner; existing owners are preserved. The response includes the whole folded comparison review state plus adjudicated_count and event_count (the number of findings changed and review log rows appended). Event ordering is preserved across all named findings.

POST /api/estate/comparisons/{diff_hash}/review/comment

Comments are append-only — they are never edited or deleted. Successful responses include data with the whole folded comparison review state. Author attribution follows the same rules as POST .../review/finding.

POST /api/estate/comparisons/{diff_hash}/review/decision

Successful responses include data with the whole folded comparison review state. When decision is awaiting_review, decided_by, decided_at, decision_run_id, and decision_population_hash are cleared. For approved / approved_with_exceptions, the server validates that the selected population is complete, has unresolved_count == 0, and records run_id / population_hash on the approval event. Author attribution follows the same rules as POST .../review/finding. Recording ANY run-level verdict requires a genuinely authenticated human: on a stack with auth enabled a system, machine or delegated identity is refused with 403. That rule covers approved, approved_with_exceptions, and awaiting_review alike: resetting a verdict is a statement about the run just as an approval is, so it is not an authority-free escape from the gate. With auth disabled the verdict is permitted and attributed locally, because every caller on such a stack already reads as the administrator.

POST /api/estate/comparisons/{diff_hash}/review/propose-rule

Drafts a reusable Π rule from the finding’s first-hand evidence and records the proposal. The rule enters the Π review worklist at pending_review (still status='proposed' — never auto-ratified), attributed to the caller, and the review-log event is written in the same transaction. Successful responses include data with the whole folded comparison review state plus proposed_rule_id (the new rule id). Author attribution follows the same rules as POST .../review/finding.

POST /api/estate/comparisons/{diff_hash}/review/propose-cluster-rule

Proposes one reusable ruling for a same-cause cluster. Each member gets its own single-column rule, drafted from its own first-hand evidence (never from the client), so every licence keeps its one-element boundary. Rules that fold the same way share a mechanism signature, so the Π worklist presents the cluster as one question over every member’s site, which one consent signs. In the same transaction, every member with no owner is recorded as owned by the caller. Successful responses include data with the whole folded comparison review state plus proposed_rule_ids, one per member, in member order. The same transaction records the ruling itself, which the state reports in cluster_rulings with the caller as owner; proposing the same members again records nothing new and keeps the first owner. All or nothing:

POST /api/estate/verifications

Run a system_diff Contract against its pre-registered source / target connections server-side, or verify a sealed compiled derivational artifact by compiled_spec_hash, and return the whole verification result. The comparison executes under the authenticated caller’s identity (connection RBAC applies), never a body-supplied bot — matching every other estate engine call. Verification references existing connections and never creates one or reads its credentials. Supply exactly one form:
  • spec — a single Contract as a JSON object (connections live inside the Contract),
  • spec_yaml — the same Contract as YAML text, or
  • compiled_spec_hash — a sealed derivational artifact in the compiled-spec store, verified by ref and expanded into one comparison per obligation.
The compiled_spec_hash form carries no connections (a blind-authored artifact only ever saw a source catalog), so source_connection_id and target_connection_id are required with it — they are injected into every obligation at verify time and must already be registered on the instance. A registered connection may be a legacy database connection or a saved Connections-API database connection; for the latter, use its <type>.<name> identifier. A saved Connections-API connection with a registered query transport, such as palantir_foundry, is also valid. Other non-queryable Connections-API entries are not valid verification bindings. Optional source_db_type / target_db_type override engine selection; when omitted, engines are resolved from the registered connection metadata (defaulting to snowflake when unknown). * Provide exactly one of spec, spec_yaml, or compiled_spec_hash. Supplying none or more than one returns 422. ** Required when compiled_spec_hash is supplied. For a by-ref request, an invariant obligation is graded wholly on one estate rather than compared source-to-target. An obligation that declares its own invariant_side in the compiled spec is graded on the side it declares; this request field is the FALLBACK for every obligation that declares none, and defaults to source. Set it to target when the target estate is the one being built and validated. The selected side’s connection and catalog are used together, so target requires target_connection_id; an unsupported value or a missing target connection returns an ERROR result for the affected invariant instead of falling back to the source estate. This selector does not change keyed obligations, which continue to compare the source and target estates. When both sides use the same registered connection, invariant_side: "target" requires target_catalog when the resolved engine can bind more than one catalog (for example, Databricks or Snowflake). On an engine known to use one database per connection, the connection itself identifies the database, so a target-side invariant may omit target_catalog and is graded against that database. An unstated engine keeps the catalog requirement because the system cannot prove that the connection is single-database. For source_catalog and target_catalog, the same admission rule applies as for the persisting compiled grade: on an engine that cannot bind a session catalog, a named value must match the database recorded by that side’s snapshot or the request returns 400 before any obligation is graded. See the compiled grade catalog contract. Inline Contract responses (spec / spec_yaml) include data with the full single-run engine result: Compiled-spec responses (compiled_spec_hash) include data with a batch result: An errored compiled-spec obligation may also include errored_sql; see the compiled-grade response for its fields. For a Foundry source with a Databricks target and keyed obligations that read dataset RIDs, verification also searches the Databricks target schemas named by the obligations’ target_sql: one information_schema.tables read per schema and one SHOW TBLPROPERTIES per listed table. A schema with more than 200 tables is skipped. Only one full stamped copy per RID is eligible; a row-limited sample, a table marked as a pipeline rebuild, or an ambiguous copy is not used. If selected, the obligation’s source SQL runs on the target connection and engine. A source-side invariant may also use a landed copy when its relation names a dataset RID and no obligation’s target-side read uses that copy; an invariant graded on the target keeps its target read. The search is best-effort; when it cannot establish an eligible copy, verification uses the usual source-side route. See the Prover guide for the full grade behavior and the Foundry migration tools guide for how copies are stamped and how to adopt a pre-existing table. For a keyed obligation that errors, the verifier checks whether its target_sql reads columns the target relation lacks. When the compiled artifact declares a target_model, only the columns that model declares for the relation are considered; when it declares none, every column the statement reads is. When a first-row read confirms that the target relation exists and lacks one of those columns, and the source population can be counted, the obligation is returned as FAIL over that population rather than ERROR: every source row is charged to the failure because the target statement could not run. No key was localized, so mismatched_key_count is null; the population is unchecked_source_rows. Its summary includes missing_target_columns (relation.column names), and also missing_declared_columns when the columns come from target_model. If the relation cannot be read, returns no row, or the source population cannot be measured, the original ERROR remains. Invariant obligations are graded as source-side column-domain checks rather than keyed source-vs-target comparisons. A valid invariant runs against invariant_relation on the source connection and reports both the population it examined in rows_compared and the violations in invariant_violation_count: a violation count of 0 produces PASS, and a count greater than 0 produces FAIL. Malformed invariants, failed queries, or invariants with no gradable constraint produce ERROR; their violation count and rows_compared are null. Invariant checks support not_null, numeric min / max, non-empty allowed_values, and finite declarations. finite is recorded but emits no predicate in this grade, so it does not by itself produce a violation. Numeric bounds and allowed_values do not count a NULL as a violation under SQL three-valued logic; add not_null when a NULL must fail. An unbindable catalog also produces ERROR; its violation count and effective catalog fields are null. The compiled-spec pass also runs a read-only degeneracy probe against the source for each obligation whose expected side is executable. The probe is capped at 20,000 rows per obligation and never changes the verdict: measured records what it read and which of the three known degeneracies it found, errored records a refused or failed probe, and unmeasured records an obligation the probe did not execute or a source it could not open. Use read_started_at and read_finished_at as the live-read window; obligations run sequentially, so the timestamps are not a claim that every read happened at one instant.

POST /api/estate/episodes/import

Import one bench loop-gate artifact bundle (report.json plus optional journal.json, loop_result.json, and compiled obligations) as a stored reconciliation episode. report is the only required artifact — a report-only bundle imports as an open episode with zero rounds. journal and loop_result carry reconcile rounds when the loop was driven; obligations upgrades finding identity from the fallback family key to the full semantic anchor. Party identities default to the journal’s; a journal-less bundle must name implementer_id and prover_id explicitly. Successful responses include data with the stored episode view (episode row, numbered issues, and append-only events journal). Re-importing the same (scope_key, source_run_id) returns the existing episode without mutation.

POST /api/estate/episodes/open

Open a reconciliation episode from a finished spec_conformance grading run. The server derives scope from the run (estate/connection plus the view’s spec_name), derives implementer-actionable findings from the run view, and allocates issue numbers (summary.numbering: "server"). Party identities fall back to estate_char.episodes.implementer_id (default "implementer-agent") and estate_char.episodes.prover_id (default "prover-agent") when omitted. A clean run opens the same episode shape with no issues, so the successful reconciliation is still recorded and can be closed through the ordinary close route. Successful responses include data with the stored episode view (episode row, numbered issues, and append-only events journal). Re-opening from the same opening_run_id returns the existing episode without mutation. Opening a new episode terminally supersedes any prior open episode for the same scope. The episode’s spec_hash is the run provenance’s compiled_spec_hash — the compiled artifact hash used for grading — not the view’s own spec_hash. If the run has no compiled_spec_hash, the episode still opens with spec_hash: null; the episode’s summary_notes records the missing identity, and the episode cannot be resumed against a spec. When the run view carries a diff_hash, the server resolves component_pair from the mediated diff and stores that pair on the episode. Drift findings link honestly to existing diff-ledger rows rather than opening duplicates, and later rounds reuse the episode’s stored pair. If the diff row is gone, linkage degrades observably via episode summary_notes without blocking the open.

POST /api/estate/episodes/open-live

Open a live derivational/bench reconciliation episode before round 1 from graded obligations the orchestrator supplies directly (each verify_against_contract verdict the loop gate collected off the instance), instead of deriving issues from a finished spec_conformance run view. Party identities fall back to estate_char.episodes.implementer_id (default "implementer-agent") and estate_char.episodes.prover_id (default "prover-agent") when omitted. Every opened issue is plain open (never pre-licensed). Eve replies, prover adjudication, and close reuse the existing POST /api/estate/episodes/{episode_id}/issues/{issue_number}/respond, POST .../adjudicate, and POST .../close routes. Successful responses include data with the stored episode view (episode row, numbered issues, and append-only events journal).

GradeFinding fields

Each item in findings for POST /api/estate/episodes/open-live and POST /api/estate/episodes/{episode_id}/rounds/live: Opened issues are never pre-licensed — state / licensor are deliberately absent from this wire shape.

POST /api/estate/episodes/{episode_id}/rounds

Append one full re-grade round to an open episode. The server derives still-failing findings from the grade run’s view under the episode’s scope (so durable finding keys line up round over round) and computes the differential. Drift findings are re-keyed against the episode’s stored component_pair before the differential is computed. For a persisted grade, closure by absence is limited to the categories covered by the run’s tiers_graded field (read from the view or its provenance): the structural tier covers structural and drift, while the derivational tier covers derivational. An open issue in a category that this round did not grade is carried forward instead of being marked resolved. A missing or empty tiers_graded value provides no category coverage, so absent issues are carried. Successful responses include data with the updated episode view (episode row, numbered issues, and append-only events journal).

POST /api/estate/episodes/{episode_id}/rounds/live

Append one live re-grade round from the orchestrator’s current still-failing obligations (the round’s grade-vector / findings delta) rather than a finished conformance grade run. The server re-derives issues under the episode’s scope (so durable finding keys line up round over round) and computes the resolved/carried/new differential. The round’s identity is its findings; a Gap-A verification run is persisted and inspectable on its own, so no grade-run reference is folded here. Successful responses include data with the updated episode view (episode row, numbered issues, and append-only events journal).

POST /api/estate/episodes/{episode_id}/close

Record the orchestrator’s terminal decision for one episode. superseded is deliberately absent — only a newer open episode sets that status. Successful responses include data with the closed episode view (episode row, numbered issues, and append-only events journal).

POST /api/estate/episodes/{episode_id}/issues/{issue_number}/respond

The implementer’s machine-channel reply for one numbered issue. actor_id is verified at the store seam against the episode’s registered implementer identity — the route performs no identity magic of its own. Successful responses include data with the updated episode view (episode row, numbered issues, and append-only events journal).

POST /api/estate/episodes/{episode_id}/issues/{issue_number}/adjudicate

The prover’s machine-channel verdict for one numbered issue. Only the episode’s registered prover may adjudicate; on agree the licensor recorded is that prover. Successful responses include data with the updated episode view (episode row, numbered issues, and append-only events journal).

POST /api/estate/findings/{finding_key}/ruling

The human court’s ruling on a contested finding (ledger disposition). The deciding human is taken from the request context, never the body; auth-off callers are attributed as local operator (auth disabled). Successful responses include data with the durable ledger row after the ruling. reconfirm_license is the one value that records no disposition. A license granted under evidence-signature version 1 whose preimage counted zero cannot be migrated onto version 2 without binding it to a fact nobody measured, so it stays pinned at version 1 carrying license_evidence_resign_blocked_basis; this act answers that question. It re-signs the license onto the evidence as it now stands under the current version, clears the basis and license_evidence_changed, stamps license_evidence_resigned_at, and records the answering identity in license_evidence_reconfirmed_by. Send expected_evidence_sig to say WHICH evidence the answer is about: a grade landing between the reading and the call otherwise moves what the licence silently covers, and the record would claim a human accepted a reading that arrived after they looked. It leaves the GRANT alone — licensed_by, licensed_at, licensed_episode_id and license_note are unchanged, because re-confirming is not re-licensing and a second license ruling would credit the original decision to whoever answered the migration question.

GET /api/estate/episodes

Returns data as an array of visible episode rows, newest first. Episodes on a comparison the caller cannot open are omitted. Without a comparison, visibility is checked against the named estate and any known recorded connection, or against the recorded connection alone when no estate is named. A page can therefore contain fewer than limit rows. Listing returns episode metadata only — call GET /api/estate/episodes/{episode_id} for issues and the journal.

GET /api/estate/episodes/{episode_id}

Successful responses include data with the episode row, numbered issues (each carrying the numeric durable_finding_id when its signature is known, plus prior_ledger_status and prior_resolved snapshots from import). Issues also include disposition, ledger_status, and counts_open: the owning finding ledger’s human disposition, its standing status, and the store’s fail-closed verdict for whether the issue still counts against the episode. counts_open is true only for an issue whose state is open and whose ledger does not carry a licensing ruling or the licensed status. durable_finding_id is the durable finding ledger’s #N, shared by diff-linked and obligation findings; use it with GET /api/estate/findings/{finding_id}/history when you need the finding’s cross-episode lifecycle. The string finding_id inside an issue is the finding signature used for obligation identity, not this numeric ledger number.

GET /api/estate/findings/{finding_key}/episodes

Successful responses include data with: The history read performs its usual ledger and episode reads, plus one latest-sighting read only when the finding is both a standing license and pinned. An unknown finding that has episode sightings but no obligation-ledger row returns license_reconfirmation.offered: false because there is no license binding to re-confirm. See the ruling endpoint above for the expected_evidence_sig precondition and the effect of reconfirm_license. The caller must be able to see the comparison that minted a comparison-minted finding; otherwise the endpoint returns 404. For other findings, only sightings in episodes the caller may see are included. An obligation-only finding with no visible sightings returns 404. When any sighting is hidden, the endpoint withholds the listed ledger fields folded over the complete sighting history rather than deriving misleading partial totals. A reconfirmation is not offered when its evidence comes from the hidden newest sighting.

GET /api/estate/findings/{finding_key}/witnesses

Successful responses include data with a bounded read-only witness document: per-side column definitions and rows (with the problem column highlighted when applicable), counts, alignment caveats, and the full plan disclosure (queries run, limits, skipped or timed-out queries). Each side’s witness metadata also reports declared_key_basis: declared when the caller supplied the key used for localization, or engine_declared when the key came from the engine. The caller must be able to see the comparison that minted the finding; an unknown finding or one on a hidden comparison returns 404. Live queries use each side’s own registered connection when it can be identified, not merely the connection associated with the shared (dialect, database) label. This keeps a SQLite-to-SQLite witness on each side’s own file even though both databases are labeled main. If the label matches multiple connections and the side’s connection cannot be identified, the service does not guess from the label; the stored boundary connection is used and any connection error appears in the query disclosure. When a stored declared key uses an expression this build cannot read, localization declines with decline_reason="unreadable_declared_key" rather than pairing rows under a different key. For a finding whose current occurrence is from the stimulus layer, the response is still successful but data.available is false: no at-rest probe is run because the injected synthetic batch has already been torn down, and the reason discloses that the at-rest result would answer a different question. When a sealed prediction receipt is present, that reason includes the receipt; otherwise it states that no behavioral evidence was recorded. For addressing_incoherence findings whose evidence has no target_object, the witness plan abstains on the target side rather than treating the target as absent. A missing target object in an incompletely enumerated characterization snapshot remains unknown, and a cached witness document that includes a target side is not reused for this abstaining recipe. For row_count, content_digest, column_content_digest, categorical_value_counts, and column_null_count findings, data.localization uses the same staged count and paged key/hash path when the safety checks pass. result_kind: "rows" returns exact source_only_rows, target_only_rows, and changed_rows when there are at most 100 differing keys; each changed row includes the source and target row plus a cells array with the differing column, source_value, and target_value. Column-content localization hashes only the named column, and null-count localization reports only null-state transitions; its finding wording says NULL values, not empty text values. categorical_value_counts instead returns result_kind: "value_count_changes" with empty row arrays and a distinct value_count_changes array. Each entry contains canonical_value, source_count, target_count, and delta (target minus source); more than 100 values on either side or more than 100 changed values declines with value_difference_fence and an empty value array. Exact key localization is available for tables up to 10,000 rows per side when the paged and independent live reads remain consistent. When more than 100 keys differ, the key comparison remains exact but row details are sampled deterministically; the payload reports completeness: "exact_keys_sampled_rows" instead of "exact". Declined results disclose a decline_reason such as table_exceeds_row_fence, budget_exhausted, concurrent_modification, or query_error, rather than returning partial row arrays. The localization fence is 100 rows per key/hash page, 10,000 total rows per side, and 100 pages per side; the connector execution ceiling is 110 rows to allow the row and grouped-value sentinel checks. column_distinct_count includes data.value_differences, which compares the distinct values on each side and reports up to 20 source_only.values and target_only.values. The payload includes compared_as, comparison_note, found, complete, and decline_reason; each direction’s total is available only when the comparison is complete. The status is "listed" when differences are listed, "no_differences" when a complete comparison finds none, and "declined" when the values cannot be listed. Values are listed only when the column resolves on both sides, the engines and column types support a shared comparison, and neither side has more than 40,000 distinct values. When a value list declines, the pane falls back to the most-frequent-value samples, paired by frequency rank. These findings still include an explicit declined data.localization payload. column_min_value and column_max_value retain their evidence panes and pair by statistic_rank; column_type_params also retains its evidence pane. For column_max_length, each side shows its own longest-value rows, paired by length rank rather than row identity; only rendered length is compared. The generated length column name appears in notes (normally witness_rendered_length, with a suffix if that name is already used). Binary row samples show size and hex up to 32 bytes, or size and a SHA-256 fingerprint for larger values; binary values in distinct-value lists use fingerprints. Column names in emitted SQL are resolved independently per estate side against that side’s latest characterization snapshot (measured column names, with declared keys as fallback), so cross-dialect comparisons use each engine’s catalog spelling — for example order_id on postgres and ORDER_ID on snowflake. When a logical column name cannot be matched unambiguously on a side, the planner omits the affected queries and records the reason in alignment_caveat and notes rather than running SQL that would fail. Samples are served from a persisted cache by default; the response may include cached and cached_at when served from cache. Read-only by construction — the planner emits SELECT / information_schema statements with hard row limits. The timeout_seconds budget is shared across all localization stages. A query that outlives the remaining budget is disclosed with timed_out: true (rather than skipped: true); an incomplete localization is therefore declined and is not reused from the response cache. Exact means complete and untruncated for the disclosed independent live reads; the two databases are not observed in one shared transaction.

GET /api/estate/findings/families/{family_signature}/witnesses

Successful responses include data with family_signature, comparison, member_count, sampled_count, and members (witness documents for an open-first sample of at most three member findings). Each member’s samples are served from a persisted cache by default. Only visible family members contribute to member_count or the sample; a family with no visible members is answered as unknown.

GET /api/estate/dossier

Successful responses include data with kind, title, summary (one-paragraph factual summary reusable by the drawer), markdown (the complete ticket-grade dossier), filename (deterministic download-safe name), cached (true when served from the persisted dossier cache; null for behavioral dossiers, which are not cached), and cached_at (ISO-8601 timestamp of when the cached assembly was first persisted; only set on a cache hit). For kind=family, the dossier carries comparison identity, the fact kind in plain language, the visible member list with per-member was→now evidence, the affected-object rollup, adjudication state + notes, the suspected fault class, and a reproduction recipe. Family dossiers are served cache-first when warm; the cache is invalidated automatically when adjudication state changes (finding or family disposition, or a note on the family). A caller-specific family read is assembled live from ledger rows whose first_seen_diff comparison is visible to that caller, rather than read from the shared cache. For kind=behavioral, the dossier carries the injected synthetic stimulus, each side’s outcome, the diverged object, the arsenal attack class/tier when recorded, a replay pointer, and a reproduction recipe. A run the caller may not see is answered as an unknown run (404). When kind=family identifies a known diff but the selected family has no members under status, the endpoint returns 404 with the message This hand-off packet has no members. and a structured error payload containing code set to "dossier_family_empty" plus identifiers for diff_hash, family_signature, and status. Clients can use this response to show an unavailable hand-off packet without rendering raw dossier internals. Unknown dossier subjects still use the generic 404 response.

GET /api/estate/arsenal/ranking

Successful responses include data with the operator_ui-redacted selection: attack chips (class, aim, potency, rationale), coverage summary, and held_out_withheld_count. Veiled recipes are stripped and held-out families are withheld (only their count is disclosed). The full recipe set never crosses this boundary — that is the evidence-ledger view.

POST /api/estate/contracts/{contract_id}/attestation

Returns run_id and a data summary. By default, export refuses while the transition has structural residue, unlicensed open behavioral findings, or unverified behavioral coverage. Setting allow_open_residue=true is an explicit human choice to mint a QUALIFIED attestation embedding every disclosure — a clean attestation is never minted over an open gate. The operator identity is taken from the authenticated session, never from the request body.

GET /api/estate/contracts/{contract_id}/attestations

Returns data as an array of visible attestation rows for the contract, newest first (lifecycle fields included). Its contract’s snapshot must be visible; if an attestation binds a comparison, the caller must be able to open that comparison as well. A statement binding no comparison is judged by its snapshot alone, and a hidden snapshot returns an empty list.

GET /api/estate/attestations/{attestation_id}

Returns data with the stored attestation document. The caller must be able to see both the comparison it binds and the snapshot its contract was compiled from; an attestation binding no comparison is judged by its snapshot alone.

GET /api/estate/attestations/{attestation_id}/report

Successful responses include data with the attestation-viewer document: grade × plane coverage matrix, qualification, waivers, and ledger-head linkage.

POST /api/estate/attestations/{attestation_id}/verify

Successful responses include data with the signature verdict and bundle re-derivation checks.

GET /api/estate/correspondence-rules

Each row includes rule_id, component_pair, status, provenance, qualification (edge-fixture results when present), and payload. When diff_hash is supplied, the response projects pair-scoped approval state for that comparison; an approval for another estate pair is not treated as active. When a qualification has per-fixture results but lacks either stored failure count, the response derives false_acceptances from licensed rejects and false_rejections from unlicensed accepts. A qualification without results is not assigned these counts. Rows also include proposer_kind (agent, person, service, or null) and proposer_bot_id (the acting bot id when known for an agent proposal; otherwise null), plus proposer_display_name (the proposing agent’s runtime bot-registry name when available; otherwise null). These fields come from trusted caller context, not a request-body value. The first registration records the proposer kind and bot id; later registrations do not replace them. A missing proposer_display_name can be filled by a later registration from the same agent when its runtime name becomes available; a recorded name is never replaced, and the name is not inferred from the bot id. agent identifies a proposal attributed to an agent, person a genuine human HTTP caller, and service a known service identity or a direct tool-endpoint call. null means the caller could not be established or the rule predates these fields. For an unbound proposal held by a live bundle Action Review request, the row also includes owning_bundle_id, owning_bundle_diff_hash, and owning_bundle_request_id; these are null when no bundle holds the rule. If the bundle is known but its request cannot be read, the bundle identifiers are returned and owning_bundle_request_id is null. owning_bundle_request_status is the request’s effective ledger status when readable, and null when no request can be named or read. Proposed rows also include sign_off_route: action_review for any rule an agent or service proposed, including data-derived or unclassified rules; otherwise direct. Ratified and retired rows return null. Use the route-specific sign-off rather than inferring it from proposer_kind. data contains only rules already in the registry. The separate built_in array contains rules the selected pair’s compares bind without proposal or ratification; they are not registry rows and are independent of registry status and comparison-specific rule scoping. When neither a pair nor a comparison is named, built_in spans every pair; otherwise it describes the resolved pair. Use GET .../pack-availability to check shipped-pack counts and POST .../propose to seed an unproposed pack. Use GET .../pack-consent to inspect the shipped pack directly, including each rule’s vendor consent_tier, its effective_consent_tier after the dialect-pair evidence gate, whether this estate pair has adopted it, and whether a one-click toggle is available. The response keeps measurement templates separate from rules; templates are reference forms, are not adoptable, and carry no consent tier.

GET /api/estate/correspondence-rules/pack-availability

Successful responses include component_pair, shipped_count (rules the shipped pack defines — 0 when none ships), proposed_count (pack rules already seeded into the registry), and unproposed_count (shipped but not yet proposed — a nonzero value drives the product propose affordance), plus built_in_count (rules the pair’s compares bind without proposal; these are separate from and never included in shipped_count, so built-in rules can exist even when all three pack counts are 0). Returns the shipped pack for component_pair, with one rules entry per shipped rule. Each rule includes its exported ref, content-addressed rule_id, consent_tier, effective_consent_tier, consent_tier_reason, optional degrade_reason, direction, reverse_would_lose, optional adoption_note, fixture_count, configuration_predicates, adopted, and adoptable. The effective_consent_tier can be lower than the shipped tier when the dialect pair does not have the evidence required for zero-click licensing. The response’s templates array contains reference measurement forms with adoptable: false; they do not license or ratify anything.

POST /api/estate/correspondence-rules/adopt

Adopts only rules whose effective tier is checkbox. The server re-qualifies each selected rule against the source and target snapshots behind diff_hash, then binds it for that estate pair. Each requested reference gets an independent result, so one failed qualification does not prevent other selected rules from being adopted. Unknown references, auto rules, and ceremony rules are returned as per-rule failures rather than silently skipped. The operation requires ratify_source_contract and a genuinely authenticated human; adopted_by comes from the authenticated user context, not the request body. Successful responses include component_pair, diff_hash, adopted_count, and a rules array. Each outcome includes ref, rule_id and title when known, adopted, and, when applicable, adopted_by or error. A rule held by a live bundle Action Review request is not adopted; its outcome includes code: "rule_held_by_bundle" and owning_bundle_id so the caller can direct the reviewer to that bundle.

POST /api/estate/correspondence-rules/accept-defaults

The batch sibling of POST /api/estate/correspondence-rules/{rule_id}/ratify. Any signed-in person can accept the listed defaults with view_connections; no special approval permission is required. Each named rule is re-qualified against the snapshots behind diff_hash before it binds, and each gets an independent verdict — an unknown id, an already-decided rule and a failed re-qualification each refuse that rule by name and leave the others to bind. The signer comes from the authenticated user context, never the request body, and the estate pair from the stored comparison, never the caller. The batch action accepts only rules whose subject is structural. A rule that licenses a statistic derived from the values — such as a minimum, maximum, or distinct count — is refused by name here because wrong data can explain that difference as readily as the two engines do. It still requires individual human review. For an agent- or service-proposed rule, use the one-rule Action Review request below. Its evidence is shown in the Prover review guide; approval re-qualifies it against this comparison before ratifying it. An agent- or service-proposed rule with sign_off_route: "action_review" is also refused in its per-rule outcome with code: "rule_requires_action_review"; ask for that rule’s approval through POST /api/estate/correspondence-rules/{rule_id}/request-approval instead. Every rule that bound is recorded in a SINGLE platform_defaults_accepted event on the comparison’s review log, returned as consent_event_id. One signature may cover several mechanisms: a mechanism is the unit of the question a consent surface asks (one checkbox each), while one press is one act and one act is one event. The event payload carries the mechanism breakdown, so a later reader can still see which distinct behaviours the signature covered. When nothing bound, no event is written and consent_event_id is null: a consent record over an empty decision would be a false record of consent. The bindings and that event are written in ONE transaction, so a binding can never outlive a failed event write and become a licence attributable to no signature. Licensing is decided at diff time, so a newly bound rule changes no number until the comparison runs again. The call therefore enqueues that re-evaluation itself, resubmitting the same comparison through the ordinary system-diff flow and carrying forward everything the stored comparison recorded about how it was mediated: its two snapshots, its delta, its addressing map (ν), its case-folding, and its recorded title with the spec kind that title was given under. The one input it does not carry is the operator’s quiescence attestation, and that is deliberate: an attestation is one person’s first-hand assertion about the one comparison they launched, so it is never replayed onto a run they did not start — accepting platform defaults approves licensing rules, it is not a re-attestation that nothing was writing. An unattested re-run therefore ABSTAINS where the attested original decided, and knows LESS than the comparison it replaces. The recompute object reports enqueued, the run_id to watch, and — when no run was started — the reason. When the comparison being re-run was decided on an attestation, quiescence_attestation_dropped names the attesters (declared_by), their note, and decided_on_attestation — how many elements the original decided on their word, which is the number this re-run cannot decide. decided_on_attestation is null when the stored record carries no readable count: the attestation is still disclosed, its size is simply unknown, which is not the same as zero. quiescence_attestation_dropped itself is null when no attestation is on record for that comparison — usually because none was made, but also when the originating run row is gone or its summary predates the disclosure. Read it as “nothing to disclose”, never as a positive claim that the original was unattested. Any refusal to start (nothing bound, a diff of the same pair already running, a snapshot that has since gone, stored mediation metadata that is not readable) is disclosed here rather than raised, because the signature has already landed and a failed response would report a consent that succeeded as an error. Automatic re-evaluation is best-effort, not a delivery guarantee. The consent commits before the submit is attempted and the two are not joined, so a request cancelled in between — or an enqueue that fails unexpectedly — leaves the rules ratified with no run. That gap is reconciled on the read: GET /api/estate/rule-reviews?diff_hash=… re-enqueues a batch consent that no later comparison of the same pair answers, once, attributed to the original signer. A consent nobody reads back is never reconciled; POST /api/estate/system-diffs remains the explicit re-run for a comparison whose numbers still look stale. Successful responses include diff_hash, component_pair, pair_fingerprint, accepted_by, accepted_count, consent_event_id, a rules array of per-rule outcomes (rule_id, title, accepted, error, and, when held, code and owning_bundle_id), a mechanisms array coalescing the accepted rules into the questions they actually asked (signature, rule_kind, label, rule_ids, sites), and recompute. When a live bundle Action Review request holds a requested rule, that rule’s outcome is refused with code: "rule_held_by_bundle" and its owning_bundle_id; other rule outcomes remain independent. If the request state cannot be read, the outcome uses code: "rule_hold_unreadable" and still identifies the bundle.

POST /api/estate/correspondence-rules/propose

Propose-class (view_connections), not ratification: seeds the pair’s shipped Π pack, edge-qualifies each rule, and places them on the review worklist. Rules are eligible and license nothing until a named human ratifies each one on POST /api/estate/correspondence-rules/{rule_id}/ratify. Pass both snapshot ids when available so qualification can record the configured pair’s environment fingerprints and rules can reach configuration_qualified (the tier ratification requires). Delegates to the same seam as the propose_correspondence_rules agent tool. The proposer identity is taken from the authenticated session (email, display name, or user id — audit only), never from the request body. Successful responses include component_pair and rules — an array of proposed rules, each with rule_id, title, qualified, provenance, and configuration_recorded when present.

POST /api/estate/correspondence-rules/{rule_id}/withdraw

Withdraws the proposal from this comparison only. It does not reject or revoke the correspondence-rule definition, remove the proposal from another comparison, or license anything. The server checks the ground rather than trusting a caller-supplied classification: foreign_origin when the recorded origin estate pair differs, outside_boundary when a legacy proposal has no recorded origin and its named object is absent from the comparison’s measured inventory, or own_proposal when the authenticated caller created the proposal. Every successful withdrawal is written to the comparison review journal and is idempotent while the withdrawal remains in force. A later proposal derived by the same comparison cancels that comparison’s withdrawal and makes the row visible again. Successful responses include rule_id, diff_hash, withdrawn_by, reason, grounds, and event_id. withdrawn_by is derived from the caller context; the request body cannot choose it.

POST /api/estate/correspondence-rules/{rule_id}/qualify

The step between propose and ratify: evaluates the rule’s edge fixtures and records the configured-pair field-survival evidence a passing evaluation earns. Fingerprints come from the two characterization snapshots behind diff_hash when the ratifier is in a comparison context, otherwise from the rule’s pair’s most recent forward diff. With fingerprints recorded the rule reaches configuration_qualified (the tier ratification requires); without them only synthetic_observed, and ratification stays refused. Successful responses include rule_id, qualified (whether the rule’s edge fixtures passed), provenance (provenance tier the qualification earned, when present), and configuration_recorded (whether the configured pair’s fingerprints were recorded). The ratifier identity is taken from the authenticated session, never from the request body.

POST /api/estate/correspondence-rules/{rule_id}/ratify

Successful responses include rule_id, pair_fingerprint (the concrete estate pair approved), and ratified_by (the resolved signer label). Ratification applies only to that estate pair; approving the same rule for a different pair requires a separate action. With authentication disabled, send the caller’s name in X-Genesis-Signed-As; it is recorded with (self-reported, auth disabled), or as local operator (auth disabled) when the header is absent. With authentication enabled, any signed-in person signs and a typed name is refused; the rule’s proposer may sign their own proposal. A person’s own PAT is accepted and attributed to its owner. Ratification refuses rules that lack a passing edge-fixture qualification. Rules whose sign_off_route is action_review cannot be ratified through this direct endpoint; request and decide their approval through Action Review instead.

POST /api/estate/correspondence-rules/{rule_id}/request-approval

Ask for Action Review approval of one agent- or service-proposed correspondence rule. The rule’s original proposer remains the proposer of record; the authenticated caller requests its approval. This stages a one-rule approval bundle and a linked Action Review item. For a data-derived or unclassified rule, the request carries the evidence shown in the Prover review guide and approval re-qualifies the rule against the comparison before ratifying it. If a live bundle request already holds the rule, the endpoint returns that request with replayed: true rather than staging another. An open agent- or service-staged disposition package owns a rule it proposed or that one of its items promotes under, and is approved through that package. A rule pinned or derived by a person’s open package can use its own request; approve the package after the rule. The response includes bundle_id, diff_hash, proposal_id, the request status and url, card_sent, and replayed. Approve the linked request in Action Review; this endpoint does not ratify the rule itself.

POST /api/estate/correspondence-rules/{rule_id}/reject

Successful responses include rule_id, review_status (revoked), and reviewed_by (the resolved signer label). Rejection retires the proposal so it stops licensing anything and leaves the pending-review worklist. The comparison is checked with the same visibility rule as its read; a hidden comparison returns 404. The rule must also be scoped to that comparison’s estate pair or it is answered as an unknown rule. When diff_hash is omitted, Genesis selects a visible comparison of the rule’s pair; if the caller can see none, the rule is answered as unknown (404). The rejecter identity is taken from the authenticated session, never from the request body. A ratified rule is revoked (POST /api/estate/correspondence-rules/{rule_id}/revoke), never rejected — the store refuses rejection of a ratified rule (400).

POST /api/estate/correspondence-rules/{rule_id}/revoke

Successful responses include rule_id, pair_fingerprint, status (retired), revoked_by (the resolved signer label), and superseded_by when set. Revocation retires the approval only for that estate pair; other pair approvals and matching code-reviewed corridor defaults remain independent. Diffs already registered that cite the rule keep their frozen verdicts. The revoker identity is the resolved signer label; with authentication enabled it comes from the authenticated principal, and with authentication disabled it follows X-Genesis-Signed-As or the local-operator fallback. This is the registry-level revoke (with required reason and optional superseded_by). For the ratification-review worklist revoke without a reason field, use POST /api/estate/rule-reviews/{rule_id}/revoke instead.

GET /api/estate/rule-reviews

Returns data as an array of Π correspondence rules for the operator ratification-review worklist. Each row includes the rule fields from the registry plus an effective review_status, viewer_signs_as, and viewer_can_sign. viewer_signs_as is the identity this caller’s review write would record when the caller has a signing identity, or null otherwise; any signed-in person can sign without a special permission, including the rule’s proposer. viewer_can_sign is whether the caller may sign that row, or null beside a null signer. A caller may read the worklist without being eligible to sign when they have no signing identity. With authentication disabled, viewer_signs_as uses the X-Genesis-Signed-As name and its (self-reported, auth disabled) disclosure, or local operator (auth disabled) when the header is absent. Rows also include server-derived mechanism_signature, mechanism_label, mechanism_provenance, scope, and mechanism_relation. The signature groups rules that ask the same consent question; the label states that question in reader-facing words; and mechanism_provenance distinguishes Platform default from a ruling (including its signer when recorded). scope identifies whether the rule applies to a pair or a single site. mechanism_relation is null when no relationship is present, or states that the rule extends the platform default when the other rules in this response establish a strict widening. usage is null on this route — merge counts from GET /api/estate/rule-usage client-side so the worklist renders immediately. With diff_hash, rows and review status are limited to approvals for that comparison’s estate pair. Unbound proposals are additionally scoped to the comparison: a recorded foreign origin or an object outside the comparison’s measured boundary is omitted, while a proposal that belongs to the comparison remains visible with outside_boundary: false and, when approval would refuse it, a non-null blocked_reason. Pair-generic rules and rules already bound to the comparison are not removed by this proposal filter. Each row also includes default_refusal, which is null when the rule may be offered in the pre-ticked platform-default set and otherwise contains the reason it must be handled through the single-rule ceremony instead. This is separate from blocked_reason: a rule can be approvable on its own while still being ineligible for batch consent. An unbound proposal held by a live bundle Action Review request also includes owning_bundle_id, owning_bundle_diff_hash, and owning_bundle_request_id; these are null when no bundle holds it. If its request cannot be read, the bundle identifiers remain available and the request id is null. With diff_hash, this read also reconciles. A batch consent (platform_defaults_accepted) on that comparison which no later comparison of the same pair answers is re-enqueued once, attributed to the original signer — the repair for a consent whose automatic re-evaluation was lost between the approval transaction and the submit. It is best-effort and cannot fail the read: an unreadable reconciliation is logged and the worklist is returned regardless. Callers that poll this endpoint should expect it to start at most one background system_diff run per consent event, never one per request.

GET /api/estate/rule-reviews/by-comparison

Returns data as a map keyed by each requested diff_hash that names a stored comparison whose source and target snapshots resolve to an estate pair the caller may see. Each value is an array of rule-review rows scoped to that comparison’s pair. Unlike the focused GET /api/estate/rule-reviews?diff_hash=…, this batched route does not include the bundle-hold fields. A requested hash that is unknown, cannot resolve its pair, or is not visible to the caller is absent from data; an existing comparison with no rules is represented by an empty array. This endpoint is intended for a review landing page that needs pending-platform- default cues for several comparison cards. Unlike the single-comparison diff_hash read, it does not run consent read-repair or enqueue background system_diff work. It requires view_connections and is read-only.

GET /api/estate/rule-usage

Successful responses include data as a map keyed by full rule_id: Unscoped counts are memoized per comparison: a new comparison folds only its own payload, while repeated reads reuse already folded comparisons. Supplying diff_hash returns counts for that comparison only. Without diff_hash, counts include only comparisons the caller may see; an unknown or unreadable diff_hash returns 404 rather than disclosing the comparison.

GET /api/estate/rule-reviews/{rule_id}

Successful responses include data with the rule’s full review dossier: payload (what the rule matches/licenses), edge fixtures (accept and reject), provenance tier, qualification, field_events, effective review_status, viewer_signs_as (the identity this caller’s review write would record, or null when they may read but not sign), viewer_can_sign, and populated usage (licensed_count, comparison_count). When diff_hash is supplied, the response also includes the normalized full diff_hash that identified the exact comparison scope. sign_off_route is action_review or direct while the rule is proposed, and null once it is ratified or retired. Under a comparison, the dossier also includes owning_bundle_id, owning_bundle_diff_hash, owning_bundle_request_id, and owning_bundle_request_status for a bundle holding the rule; they are null when no bundle holds it. If the bundle is known but its request cannot be read, the bundle identifiers remain available while the request id and status are null. For a still-proposed rule withdrawn from the comparison identified by diff_hash, withdrawn_here contains withdrawn_by, reason, grounds, and withdrawn_at (ISO 8601 when available); otherwise it is null. The withdrawal keeps the rule off that comparison’s worklist until it is proposed again. On an authentication-disabled stack, viewer_signs_as is the self-reported signer label from X-Genesis-Signed-As, or local operator (auth disabled) when the header is absent.

POST /api/estate/rule-reviews/{rule_id}/confirm

Successful responses include rule_id, pair_fingerprint, review_status (confirmed), and reviewed_by (the resolved signer label). Confirmation is additive for that estate pair — the approval stays ratified and keeps licensing future diffs for that pair. The reviewer identity is the resolved signer label: the authenticated principal when auth is enabled, or the disclosed self-reported/local label when auth is disabled.

POST /api/estate/rule-reviews/{rule_id}/revoke

Successful responses include rule_id, pair_fingerprint, review_status (revoked), and reviewed_by (the resolved signer label). Revocation retires the human approval only for that estate pair; matching code-reviewed corridor defaults retain independent authority. Existing diff records remain immutable evidence. The reviewer identity is the resolved signer label: the authenticated principal when auth is enabled, or the disclosed self-reported/local label when auth is disabled.

GET /api/estate/waivers

Returns data with classified waivers (owner, expiry, scope) and summary counts across deltas the caller may see. The ledger also includes waivers granted in comparison review: one-time exceptions and waived finding dispositions, with any recorded owner, approver, expiry, and comparison/finding scope. Older waivers without recorded terms remain as recorded; no expiry is inferred. A waiver whose recorded expiry has passed no longer licenses the exception.

POST /api/estate/deltas/preview

Dry-compiles the draft Δ against the contract. Successful responses include data with footprint vector, validation issues, touched obligations, coverage impact, and consumer impact. Nothing is persisted.

POST /api/estate/deltas

Successful responses include delta_hash (content-addressed id) and data with the proposal summary (status, operation_count, already_existed). The operator identity is taken from the authenticated session, never from the request body. Ratification remains a separate human-gated route.

POST /api/estate/deltas/{delta_hash}/ratify

Successful responses include delta_hash and ratified_by (authenticated human). The ratifier identity is taken from the authenticated session, never from the request body. The empty delta is explicitly ratifiable.

GET /api/estate/knowledge

Filters are ANDed exact matches (unset = wildcard). Successful responses include data with the operator_ui redaction: atoms (each with atom_id, scope, phenomenon class labels, provenance, disclosure, and yield_stats — never evidence refs), returned_count, held_out_withheld_count, and disclosure_note. Held-out atoms never appear in atoms; only their withheld count is disclosed.

GET /api/estate/knowledge/coverage

This endpoint does not document any request body or query fields. Successful responses include data with the corpus-wide inventory: persisted (the live corpus), harvestable (what a sweep would mint from current store state), gap (harvestable but not yet persisted), source_counts per collector, and fully_swept. Each tally is bucketed by phenomenon kind, provenance tier, disclosure, component pair, and source instrument.

GET /api/estate/knowledge/comparison

Successful responses include data with contributed (redacted atoms this pair’s own artifacts minted) and informing (all scope-matched atoms shaping this pair’s instruments — the jitter prior and arsenal ranking), plus counts for each bucket. Both views are operator_ui-redacted; held-out atoms surface only as a withheld count.

POST /api/estate/knowledge/collect

This endpoint does not document any request body or query fields. Runs one idempotent knowledge sweep — a deterministic re-derivation of the corpus’s abstracted knowledge. Re-running on unchanged store state inserts nothing. Successful responses include data with atom_count, per-source counts, and inserted / skipped / superseded totals. The operator identity is taken from the authenticated session, never from the request body.

Dataset inventory endpoint

GET /api/datasets

This endpoint does not document any request body or query fields. It returns success plus a datasets array of DatasetInfo objects. This endpoint does not refresh datasets or trigger retention cleanup; cleanup runs separately through the configured background cleanup job.

Dataset detail endpoint

GET /api/datasets/{dataset_id}

In addition to success and dataset, the response can include these top-level fields:

Built-in genesis_cost_usage_cube_v1

The protected Cost & Usage cube (genesis_cost_usage_cube_v1) materializes three system-metrics sources: cost_usage_cube → cost_usage_cube_v1, users_dim → users_dim_v1, and scan_usage → scan_usage_v1. The scan_usage_v1 table holds Twin scan LLM spend at hourly grain with token counts and USD cost columns (cost_input_usd, cost_output_usd, cost_cache_read_usd, cost_cache_write_usd, cost_total_usd). Cost columns are NULL when a row cannot be priced (for example an empty model). Scan-cost views on the preset: top_threads_by_cost_v1 sums cost_usd for llm_call and metered_service_request rows; the total remains NULL when no call has a known price. Its unpriced_call_count and unknown_price_call_count describe those same rows and are complete only when pricing_counts_complete is 1. The view counts tokens from those call rows and chat turns from chat_turn_activity. Twin scan facts are written to SCAN_USAGE_FACTS by the twin_scan_cost_sync background job before dataset refresh materializes scan_usage_v1. The genesis_cost_usage_dashboard app exposes a Scan Cost tab over these views.

Internal cost accounting endpoints

These read-only endpoints back the serving instance’s internal Costs screen. They never accept an instance identifier. Summary, activity, execution, facts, and calls reads are scoped to the serving instance. The gap ledger has no instance column, so its rows are not scoped to the serving instance and may include other instances’ facts on a shared store. All routes except the gap ledger require ACCESS_PULSE_PANEL; the gap ledger requires ACCESS_CONFIG_PANEL. They are available on every install. These internal routes are excluded from the published OpenAPI schema. The producer path value is chat or context_graph. Unattributed spend can exist under both producers, so producer-qualified paths keep those activities and their drilldowns separate. The older producer-less facts and executions paths resolve only when the activity belongs to one producer; otherwise they return 409 with code: ambiguous_activity_producer. The gap-ledger route accepts only administrators with ACCESS_CONFIG_PANEL. Its reason is one of post_disable, late_after_seal, revised_after_seal, expired_summarized, or legacy_unproven_residual. The windowed summary’s gap_ledger.legacy_unproven_residual discloses what pre-068 legacy rows of released open days counted beyond the raw facts that still prove them; this residual is never included in any total. Its fact_count counts residual rows (one per legacy row key, or one per day when the shortfall names no key), and row_count counts unproven facts. Fact rows for this reason use producer ~unknown, because those legacy rows named no producer. Because the ledger has no instance column, treat its results as shared-store evidence, not as facts for the serving instance alone. Each gap-ledger response contains the echoed reason, a facts page, and the standard page cursors. A fact identifies the producer, fact_id, identity, reason, and UTC event day. measures contains row_count, request_count, prompt_tokens, completion_tokens, cache_read_tokens, cache_write_tokens, and known_priced_total_usd when the ledger recorded them; otherwise it is null. epoch_start_utc is null when the fact has no disable epoch. The page is deduplicated to the latest version of each fact identity. The limit and cursor fields described below page through these records. The source sweep endpoint takes source_id as a path parameter (1–2048 characters) and has no query parameters. It reads Twin on demand for the selected source; it does not retrieve scan history or queued object names. The response contains source_id, available, effective_budget, last_outcome, and budget_has_room_now. effective_budget has nullable max_active_seconds, max_objects, max_queries, and max_containers limits; null limits are unbounded. last_outcome is null when no cycle outcome has been recorded, or contains status (in_progress, complete, incomplete, or halted), stop_reason (drained, time_slice, token_cap, budget_exhausted, superseded, throttled, or cancelled), recorded_at_utc, and budget_exhausted_units, and usage. usage is null when Twin recorded no usage or the recorded usage cannot be read; otherwise it contains the sweep’s total active_seconds, objects, queries, and containers across its cycles as of that recorded outcome. These are counts and seconds, not cost amounts. budget_has_room_now is null when that check does not apply. When Twin cannot provide a valid response, including for an unregistered source, available is false and the budget and outcome fields are null. The windowed endpoints accept these shared query fields: Custom windows must include both endpoints and fit within the supported 400-day range. A window that is incomplete, too wide, outside the supported range, or ends in the future returns 422 rather than being silently truncated. The response reports the requested window and the effective_window actually summed after snapping to the rollup grain. Windowed responses also include instance_id, instance_name_generated, unsupported_substrate, evidence_level, and producer freshness. instance_name_generated is a boolean that is true when instance_id is a generated fallback for an install with no configured name; it defaults to false. Every cost response envelope also includes rollups_ready. It is false before the rollup job’s first run has created its read models, or on an unsupported storage substrate; in either case, the response contains no figures, returns empty lists where applicable, and does not treat the result as zero spend or an error. Each producer freshness entry includes advisory as a string or null (default null). dimension_rekey_incomplete means some older days’ breakdowns could not be rebuilt; last_error names those days. This advisory does not change the producer state, and totals are unaffected. over_cap_day_lagging accompanies a stale state whose cursor is current when a day over the complete-day cap has been written since its rebuild began and the rebuild has stayed open for more than two job intervals; that day’s spend then trails its writes, and last_error names the day. A quiet backlog does not set this advisory, nor does it appear while another day holds the source. Every cost response envelope also includes recording_since, the store’s recording cutover timestamp, or null before the store exists or on an unsupported storage substrate. Windows starting before this timestamp are clamped to it. The summary’s excluded_legacy reports spend recorded since the cutover with no known instance beside the total, never in the total; it is null when there are no figures. Producer freshness state can be healthy, stale, error, disabled, unknown, unsupported, or preparing. When totals is present, known_priced_total_usd includes only catalog-priced facts. estimated_price is separate and contains usd, row_count, and request_count for facts priced at an estimated rate (including a default rate or a price whose source was not recorded); those dollars are not included in the known-priced total. unpriced separately counts facts with no price. The summary response’s series is null when rollups are not ready or the substrate is unsupported. Otherwise it contains grain (hour or day), a bucket_count, and dense buckets covering the effective window. Each bucket has from_utc and to_utc bounds clipped to that window, complete, known_priced_total_usd, estimated_usd, total_tokens, reserved_tokens, unpriced counts (row_count, request_count, and total_tokens), and fact_count. A bucket is complete only when both sources have daily rows and every such row is sealed for that bucket’s UTC day; otherwise its figures are the amounts rolled up so far. known_priced_total_usd is catalog-priced spend; estimated_usd is estimated-price spend reported apart from that total. Reserved tokens are never priced, and unpriced usage is excluded from known-priced spend. On a stable snapshot, bucket sums match totals. rollup_write_position_utc reports the older rollup write position, or null when either position is unknown; it indicates progress, not event-time completeness. Trace endpoints (activities, activity facts, activity executions, and execution calls) also accept these scope and dimension query fields: An other_scope token is tied to its instance, window, and tier. A stale token returns 409 with code: stale_other_scope. Trace filters are non-empty and limited to 255 characters; enum filters accept only the values listed above. The summary endpoint accepts include_breakdowns (boolean, default false) and breakdowns_only (boolean, default false). When include_breakdowns is true, the response includes breakdowns; otherwise it is null. The object has operation, origin, call_type, and source dimensions. Each dimension contains entries, entry_count, and truncated; each entry reports its key and key_state (value, group, not_recorded, or other), known_priced_total_usd, estimated_usd, total_tokens, prompt_tokens, completion_tokens, cache_read_tokens, cache_write_tokens, reserved_tokens, request_count, fact_count, and unpriced (row_count, request_count, total_tokens). An other entry can carry an other_scope token for filtering its complement. Each dimension is capped at 50 entries, with excess values folded into its final other entry; rekey_complete reports whether older dimension keys were rebuilt. When breakdowns_only is true, it implies include_breakdowns and returns the breakdowns without reading totals, excluded_legacy, unsummarized_post_disable, gap_ledger, or series; those fields are null and the response echoes breakdowns_only: true. This is not a summary with no recorded spend. This mode uses three SELECTs; requesting include_breakdowns on the regular summary adds one full-key rollup-tier read. Activity, facts, executions, calls, and gap-ledger endpoints add cursor pagination: Each paginated response includes page.limit, page.next_cursor, page.truncated_for_size, and page.cursor_unavailable. Response bodies are limited to 250,000 bytes; when that budget ends a page before its requested limit, truncated_for_size is true and next_cursor continues after the last returned row. If the last returned row’s key cannot be encoded within the cursor limit, cursor_unavailable is true and the list ends without a usable cursor. A malformed cursor or one issued for a different list returns 422. An individual row that exceeds the 20,000-byte row ceiling is shortened to fit; its truncated_fields names any shortened fields. The activity row’s producer is part of its identity. Use the producer-qualified facts and executions paths when a producer-less activity path is ambiguous. Those facts routes accept view (compact or full, default compact) and unexecuted (boolean, default false; when true, restricts buckets and raw facts to facts not marked as execution-bearing), in addition to the window, pagination, scope, tier, and trace-filter fields. They return rollup buckets, retention/completeness counts, and one bounded page of retained raw facts. Executions routes also accept include_reconciliation (boolean, default false). When requested, the response includes reconciliation totals; when omitted, those lines are null. This option adds one pass over the activity’s rollup-tier window. A producer-less execution request that matches multiple producers returns 409 with code: ambiguous_activity_producer. Activity rows include recurring (boolean or null). It is true for a cron or interval scheduler job, false for a one-shot (date) job and every non-job activity, and null when the job’s trigger snapshot is missing or unknown. The calls endpoint uses the opaque execution_key returned by the execution list and accepts limit and cursor plus: It also accepts an optional window (preset, from_utc, and to_utc) and the trace scope, tier, and dimension filters above. Unlike the other windowed routes, calls omit the window by default; a filtered calls request needs its parent window when it uses dimension filters, other_scope, or an explicit tier. Pass the execution key unchanged: older keys may use the colon-delimited form, while producer-qualified keys use the v2: form. A producer-less key that matches more than one producer returns 409 with code: ambiguous_execution_key. Compact call rows include cache_read_tokens and cache_write_tokens alongside the prompt and completion token counts. Context Graph bucket rows also include reserved_tokens, the embedding tokens reserved against the cap and never priced. Cache and reserved token counts can be null when they were not recorded; the Costs screen displays those values as a dash rather than zero. An unknown execution or activity returns 404. A malformed execution key or an otherwise unsupported report request returns 422. Calls report calls_completeness as complete, partial, or expired; an expired execution returns 200 with no raw calls and marks the evidence as expired. The control-state response carries evidence_level: null; its Context Graph cap includes can_edit, can_edit_reason, and the path to the existing Digital Twin cap editor, and this API provides no write route. can_edit_reason is null when editable; otherwise it is context_graph_unavailable when Twin did not respond, or requires_permission:access_config_panel when the caller lacks that permission. A dataset refresh keeps the instance column as it has always been, the agent id, so saved views, filters and joins on it are unchanged. The install a fact was recorded on is the install_instance column: the fact’s own instance, or legacy-unknown for a row written before the contract. A source’s instance parameter still selects an agent; its install_instance parameter selects an install. Canonical cost-fact rows retain the following activity, lineage, and rollup fields for consumers of the materialized dataset: The cube includes provider usage from auxiliary and background LLM calls, successful non-token metered-service requests, and ordinary chat, tool, and thread activity. Use usage_kind to tell metered-service requests from LLM calls. Non-token requests are materialized as llm_call rows, counted as calls and chat turns as they always were; the usage_kind column is metered_service_request on them (llm_call on an LLM call). Their cost_total_usd remains NULL because Genesis does not estimate the provider’s charge. When a producer supplies a synthetic thread label, Genesis keeps that label in thread_id for traceability but does not treat it as a conversation thread in canonical rollups. For activity caused by a scheduled prompt, Genesis carries a snapshot of the scheduler job and run into the cost fact. The snapshot is accepted only from trusted internal-service WebSocket callers, not from browser-supplied message metadata. A snapshot without scheduler_job_id is discarded; a snapshot with a job id but no run id is retained and represents job-level cost without a specific execution. These fields appear in the materialized dataset after a dataset refresh. A thread with a persisted summary reports that summary’s values. A thread without one gets a summary synthesized from the window’s chat facts, with no child rollup. With an install_instance filter, persisted summaries are not read, because they record no install: every summary is synthesized from that install’s facts.

Dataset refresh endpoint

This route is mounted under /api/datasets/....

POST /api/datasets/{dataset_id}/refresh

When only_source_ids contains a source that is not present in the dataset definition, the 400 detail payload includes these fields:

Dataset delete endpoint

DELETE /api/datasets/{dataset_id}

On success, this endpoint returns success: true and the deleted dataset_id. When deletion is blocked or requires confirmation, the error detail payload can include these fields:

Dataset datasource endpoint

Use this AG Grid server-side row model (SSRM) route when the client needs server-side grouping, sorting, pivoting, or tree expansion against a dataset table or view.

POST /api/datasets/{dataset_id}/datasource

The response returns QueryResultSSRMResponse fields such as rows, lastRow, optional pivotResultFields, optional secondaryColumns, and, when available, the executed sql plus original base_sql.
POST /api/datasets/{dataset_id}/query is intentionally unsupported. Genesis returns 400 Bad Request with details.unsupported_endpoint, suggested_endpoint, suggested_method, suggested_body, and alternatives[] that point callers to /tables/{table_name}/rows, /views/{view_id}/rows, or /datasource.

Dataset row endpoints

These routes return one page of materialized dataset rows for a table or saved view. columns is returned only on the first page (page = 0) to avoid repeating schema metadata on later pages.

Shared dataset row request fields

Camel-case request aliases pageSize and includeTotal are also accepted on these row endpoints.

Dataset row response fields

Genesis App registry writes

POST /api/apps, POST /api/apps/{app_id}/rename, PATCH /api/apps/{app_id}/metadata, and DELETE /api/apps/{app_id} apply registry changes only after the registry is saved. If the save is unavailable, these endpoints return HTTP 503 and do not apply the requested registry change; retry after a short wait. A create using an ID whose previous app is still being removed returns HTTP 409; retry after cleanup finishes. POST /api/apps accepts an omitted request body; Genesis uses the create request-field defaults documented in Genesis Apps. POST /api/apps/{app_id}/rename accepts display_name. Unknown request fields return HTTP 422 without changing the name; for example, title is not a rename field. Use PATCH /api/apps/{app_id}/metadata to set the metadata title (or its display_name alias). DELETE /api/apps/{app_id} preserves the workspace by default. Add the delete_workspace=true query parameter to also remove it and purge the app’s saved versions, Production assignment and promotion history, and file-level change history before removing the registry entry. If durable storage is unavailable or the apps repository is still being restored, the delete returns HTTP 503 and the app remains registered; retry after the store is available. Databricks Apps also rebuild missing saved-version files from durable Git when an operation needs them; see Genesis on Databricks. See Databricks durable-store behavior.

Genesis App Git history and revert

On macOS without an installed Git executable, the app history route uses snapshots instead of attempting to run the /usr/bin/git developer-tools stub. In a Databricks App, agent Git refuses operations on repositories with unsupported local or worktree configuration, submodules, or an incomplete safety check. The revert route returns HTTP 409 with the refusal detail. See the Git policy.

Genesis App status health fields

The versioned-app responses from GET /api/apps and GET /api/apps/{app_id}/status include data-layer health fields. This keeps a running app whose own Genesis API credential was recently refused from appearing healthy merely because its process still answers requests. For the refusal symptoms, log correlation, and recovery steps, see Diagnosing a data-layer auth failure.

Genesis App list stop reason

Each GET /api/apps list record includes the app’s last stop reason:

Genesis App proxy behavior

When a browser request is proxied through /api/apps/{app_id}/proxy/... or /api/apps/{app_id}/remote/proxy/... and the target app has dataset bindings, Genesis can mint a short-lived viewer-scoped app token and forward it upstream to the app as X-Genesis-App-Token. Browsers do not need to set this header themselves. When auth.enabled is true and auth.provider is hosted, the proxy removes the inbound session credential headers (Authorization, Cookie, X-CSRF-Token, and X-XSRF-Token) and hosted identity headers before forwarding the request to the app. Hosted identity headers include names beginning with Sf-Context-Current-User, X-Auth-Request-, X-Forwarded-, or X-Genesis-Hosted-. Genesis then sets X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Prefix from the proxied request, so a consumed hosted assertion or caller-supplied forwarding identity is not exposed to the app. When auth.enabled is true and auth.provider is proxy, the proxy also removes the inbound session credential and hosted identity headers. It removes the configured proxy-auth user, email, groups, display-name, and access-token headers, including names without a recognized prefix. X-Forwarded-Access-Token is never forwarded to app code. Genesis sets X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Prefix itself rather than passing through caller-supplied values. If the proxy cannot resolve GenesisApp authorization configuration because the configuration has not been initialized yet, the proxied request returns 503 with the detail GenesisApp authorization configuration is temporarily unavailable. Retry the request after authorization configuration initialization completes. Successful app-proxy responses identified as PDFs are returned as downloads: Genesis sets Content-Type: application/pdf, adds X-Content-Type-Options: nosniff, and uses an attachment Content-Disposition with a sanitized filename. A response is treated as a PDF when its media type is application/pdf, or when the requested path ends in .pdf and the response has no media type or uses application/octet-stream. Other responses and non-2xx responses retain their upstream headers.
When app code is handling one of these proxied requests and uses Python requests or stdlib urllib.request to call GENESIS_API_BASE_URL or GENESIS_API_BASE, Genesis also auto-forwards the current app token by adding X-Genesis-App-Token to outgoing /api/... requests to that same Genesis API origin. Existing outgoing headers are preserved; the runtime patch overrides any stale token the app attached.Long-lived microapp processes (Dash, Flask, and similar) that keep running after start get this forwarding automatically at interpreter startup via sitecustomize.py in the microapp runtime-support shim. Versioned apps get the same shim for every declared runtime type, so their process token is proactively renewed while the app remains running. On proxied requests it injects the inbound viewer-scoped X-Genesis-App-Token header Genesis minted for the current browser session. On background-thread calls with no Flask request context it uses the rotating process token, or falls back to the startup GENESIS_APP_TOKEN env var. Token precedence is: inbound viewer token → rotating process token → GENESIS_APP_TOKEN.Locally hosted apps load the shim from the Genesis server workspace overlay. Remote deployments use the same shim in the genesis-app-runner container image (container/Dockerfile.app-runner adds runtime_support/ to PYTHONPATH).Genesis mints the process token with a default 6-hour TTL (21600 seconds). When GENESIS_APP_ID and GENESIS_APP_TOKEN are set, the runtime proactively rotates the process token via POST /api/apps/{app_id}/app-token/rotate about 10 minutes before expiry so dataset and /api/query calls do not start returning 401 after the original TTL.Runtime environment flags (string; default true unless noted):For both flags, Genesis treats 1, true, yes, on, and enabled as true and 0, false, no, off, and disabled as false.Custom app code that manages its own token lifecycle can call POST /api/apps/{app_id}/app-token/rotate directly (see below). Otherwise rely on the runtime patch or send GENESIS_APP_TOKEN explicitly on each call.The same runtime patch records outbound Genesis API calls in the app’s captured stdout/stderr with the [GENESIS_APP_API] prefix. HTTP failures are logged at WARNING without the query string, so an operator can correlate a refused token call with the app’s STATUS health signal without exposing proxied viewer tokens.
The local proxy can auto-start a stopped local runtime on HTML navigation. It also recovers stale running records after a Genesis server restart: status checks for running require the recorded internal port on 127.0.0.1 to be reachable (0.25 s timeout) in addition to PID liveness; starting checks PID only. When a proxied request finds the local port dead, Genesis invalidates the short-lived proxy status cache, reconciles status, and — for HTML GET requests — returns the same activation page used for stopped apps so the runtime restarts without a manual start call. Non-HTML proxy requests in that case return 503 with detail GenesisApp runtime is restarting after a server restart. Reload the app to continue. When the upstream accepts the connection but exceeds the proxy timeout, Genesis returns 504 with detail GenesisApp timed out while responding. Please retry. and does not reconcile the runtime to crashed. The remote proxy only forwards to an existing remote deployment and keeps the Genesis proxy path stable even when the backend also publishes a direct URL. For a top-level browser GET to a production proxy URL without a valid user credential, deployments using the interactive auth0 or oidc provider redirect the browser to /apps/ with the app ID and original proxy path. The Apps page resolves a fresh _auth token after sign-in and returns the browser to that same-origin app proxy path. This redirect is limited to page navigations (Sec-Fetch-Dest: document, or Accept: text/html when fetch metadata is absent); it does not apply to iframe, fetch, or non-GET requests. The proxy accepts the returned _auth token into an app-scoped, HTTP-only cookie and redirects again without the token in the URL. The cookie lasts one hour. A proxy request that already carries _auth is not redirected to sign-in again if that token is refused. This sign-in flow does not grant app access: normal app view checks still apply, and other auth providers do not use this redirect. If an HTML navigation reaches a shared app link without view access, Genesis returns a friendly access-denied page with the same 401 or 403 status. Non-HTML requests still receive the standard HTTP error response. App view access can come from ownership, explicit app grants, or shared-role visibility when the caller and app owner both hold a role that includes view_shared_role_resources. That widening applies only to App view; refresh, edit, and manage still require the normal owner or explicit policy path.

Genesis App server token capability manifest

A GenesisApp’s server authenticates to the Genesis REST API with X-Genesis-App-Token. That token is deliberately not a general Genesis API credential — it reaches a fixed set of platform endpoints. Fetch this manifest at authoring or start-up time instead of guessing routes; anything absent from the returned capabilities list is refused when called with an app token, no matter how the request is written. For the wider API, call it from the app’s front-end with the signed-in user’s session (fetch with credentials: 'include'), which is also what keeps a human in the loop on irreversible actions. The contract is identical for every app. The path parameter app_id is accepted for URL symmetry but is not resolved — that keeps this endpoint from doubling as a way to test whether an app exists. When a valid app token is presented on a path outside this contract (and no user credential is also present), Genesis returns 403 with a message that points callers to this manifest instead of falling through to a generic 401.

GET /api/apps/{app_id}/capabilities

Current capabilities entries (as of version "1"):

Genesis App token rotation endpoint

Long-lived app workers call this route to mint a fresh GENESIS_APP_TOKEN without restarting the process. The microapp runtime also calls it automatically shortly before the current process token expires (see Genesis App proxy behavior).

POST /api/apps/{app_id}/app-token/rotate

Send the current app token in the X-Genesis-App-Token header. Genesis accepts tokens that expired within the last 48 hours (172800 seconds) so workers can recover after a brief outage without a full app restart. Request body is optional empty JSON ({}).

Genesis App LLM endpoints

App code should send X-Genesis-App-Token on both routes. Like other app routes, the token’s app_id must match the path app_id.

POST /api/apps/{app_id}/llm/call

If thread_id is omitted, Genesis creates a new thread. New threads seed their initial model_tier and provider from the app authoring thread when available, otherwise they fall back to main and no stored provider override. Existing threads keep their current model settings unless model_tier or provider is supplied in the request. It returns these top-level fields:

POST /api/apps/{app_id}/llm/direct

When model_tier and provider are omitted, Genesis inherits the app authoring thread defaults when available; otherwise it falls back to main plus the deployment’s configured main provider. It returns these top-level fields:

Genesis App validation endpoint

POST /api/microapps/{app_id}/validate is a legacy alias for the same behavior.

POST /api/apps/{app_id}/validate

Response fields include success, app_id, optional runtime_type, checks, errors[], warnings[], hints[], and error. POST /api/apps/{app_id}/start and POST /api/apps/{app_id}/restart also accept validate_slide_range (string or null, default null) and validate_stage_sweep (boolean or null, default null) when validate_before_restart=true, forwarding the same targeted deck checks through the automatic pre-start validation pass. On successful START and RESTART responses, hints contains outcome guidance for a person operating the app. caller_hints is separate API-caller guidance about the validate_before_restart and auto_capture_after_restart request parameters and the capture result; either field is null when it has no entries.
For presentation_deck apps, validation warnings can include slide.tts_alias_without_canonical_n when a slide uses narration, speaker_notes, notes, voiceover, or script without canonical n. That warning does not block validation by itself, but TTS generation skips that slide until narration is moved into n. When narration intent is explicit (for example the deck declares audio.format or metadata audio_required=true), missing fallback narration audio is promoted from a warning to a blocking validation error.
For presentation_deck apps, string-valued slide.t entries still warn when they contain HTML or entity-like markup, because plain title strings render as escaped text. To opt into safe title markup, use an object with exactly one key: {"html":"<span>Styled</span>"} for markup or {"text":"Plain title"} for escaped text. Invalid title objects surface slide.title_object_invalid. Python slides and stages must set py or python to a workspace file under presentation_slides/*.py that defines callable render(ctx). Missing files, path escapes, or renderer validation failures surface as slide.python_source_missing, slide.python_import_failed, stage.python_source_missing, or stage.python_import_failed. Outside a Databricks App, validation imports the renderer and checks its callable signature. In a Databricks App, Genesis parses and compiles the source without executing it; the renderer must be an undecorated top-level function accepting a context argument, and import errors appear only when the slide renders. These renderer files load as standalone workspace scripts, so use absolute imports such as from presentation_slides.utils import helper instead of relative imports.presentation_deck workspaces also ship scaffolded presentation.js and presentation_styles.css runtime assets referenced by app.py. Genesis copies them into the live workspace on start when missing, and treats edits to those files as restart-relevant alongside app.py and requirements.txt.

Genesis App screenshot capture

POST /api/apps/{app_id}/capture

The response’s durability_warning string is present when a screenshot selected for durable recording could not be stored or attached; a capture not eligible for recording has no warning. The request also accepts capture options including bot_id, include_visual_description, runtime_snapshot_mode, slide, stage, slide_range, stage_sweep, and render-wait selectors/signals. The response includes screenshot paths and render diagnostics when available. Captures that time out before finishing or are replaced by a retry are not kept. See Durable chat uploads on Databricks.

Genesis App dev-session remediation responses

POST /api/apps/{app_id}/dev-session uses the same structured remediation payload described in Blueprint repository sync and dev-session remediation when workspace setup is blocked by an actionable saved-connection problem. OAUTH_RENEWAL_REQUIRED responses use HTTP 409 and return the remediation fields under the FastAPI error detail body.

Genesis App remote deployment endpoints

POST /api/apps/{app_id}/deploy

POST /api/apps/{app_id}/remote/restart accepts the same spcs_mode, token_ttl_seconds, and acknowledge_identity_delegation fields. It reuses the app’s persisted remote backend and target. Genesis App list, detail, and status payloads can now include remote_deployment plus a targets object with local and remote summaries. Each target summary can include proxy_url, target_kind, target_identifier, target_label, backend and URL metadata, and version details when available. When clients need to bypass the caller-scoped apps list cache after an app was created or updated through chat, MCP, or another out-of-band flow, call GET /api/apps?refresh=1. Truthy refresh values such as 1, true, yes, or on invalidate the cached list before Genesis rebuilds the response. On registry load, Genesis renormalizes persisted microapp keys to the canonical app_id form (lowercase letters, numbers, and hyphens; 1–64 characters; must start and end with a letter or digit). Workspace directories discovered on disk are registered under the same canonical id. Duplicate registry rows that collapse to one canonical id after renormalization are skipped so a single bad key cannot hide other apps from GET /api/apps or apps_action(LIST). On Databricks Apps with durable storage enabled, the GenesisApp registry is uploaded to Lakebase before each save and restored before the server serves requests, so the app list survives host replacement. See Durable SQLite stores on Databricks. In SQLAlchemy metadata mode, App and legacy dashboard categories are stored in the metadata database and share database-assigned category IDs. Those IDs remain stable across server restarts while the metadata database is retained.

Context Graph endpoints

These routes are mounted under /api/context-graph/... and back Digital Twin scan administration in the web client. Unless noted otherwise, they require access_config_panel. Lineage-backed Context Graph operations can return HTTP 503 Service Unavailable; see Digital Twin and Context Graph for the disabled-server condition and recovery steps.

Twin log endpoints

GET /api/context-graph/logs/twin reads the tail of twin.log from the Genesis host. The file lives on durable storage alongside the context graph ({STORAGE_PATH}/twin/twin.log), so it survives pod restarts; the TWIN_LOG_FILE environment variable overrides the location. Thread-export support bundles and the Digital Twin log viewer use the same resolver, so the path cannot drift between writer and readers. File rotation is bounded via TWIN_LOG_MAX_BYTES (default 52428800, 50 MiB per file) and TWIN_LOG_BACKUP_COUNT (default 10). Genesis keeps the active file plus up to TWIN_LOG_BACKUP_COUNT rolled backups, capping total disk at approximately (TWIN_LOG_BACKUP_COUNT + 1) × TWIN_LOG_MAX_BYTES (~550 MiB with defaults) while retaining well over a week of sustained high-rate scanning. GET /api/context-graph/logs/twin/download returns the full file as a text/plain attachment named twin.log.

GET /api/context-graph/logs/twin

Response fields: Returns 404 when twin.log does not exist.

GET /api/context-graph/logs/twin/download

This route does not accept query parameters. Returns 200 with Content-Disposition: attachment; filename="twin.log" on success. Returns 404 when twin.log does not exist.

Scan permission check endpoint

POST /api/context-graph/check-permissions resolves a saved connection on the Genesis side and asks Twin to audit whether the credentials can perform a lineage scan. Genesis passes the resolved locator to Twin so the check does not require Twin to call back into Genesis.

POST /api/context-graph/check-permissions

Response fields: When connection resolution fails, Genesis returns success: false with a single checks entry named Resolve connection and status: fail.

Internal Digital Twin integration endpoints

These internal routes are mounted under /api/internal/... and are intended for the Digital Twin MCP subprocess rather than interactive client use.
All internal Digital Twin integration requests require an Authorization header with the internal bearer token: Bearer <internal-token>.

POST /api/internal/resolve-llm-credentials

It returns these top-level fields:

Internal Digital Twin integration example request

Internal upgrade-check endpoints

These routes are for the Control Plane’s post-upgrade checks, not interactive client use. Genesis mounts them only when GENESIS_UPGRADE_CHECK_TOKEN is set to a non-empty value when the app starts; otherwise the routes do not exist. Every request requires Authorization: Bearer <GENESIS_UPGRADE_CHECK_TOKEN>. Hosted mode does not expose /api/internal/, so these routes are unreachable there. Both POST routes accept exactly this JSON body; extra fields are rejected: Repeated starts with the same probe ID return the existing record. Probe records are held in separate in-memory stores of up to 32 Eve and 32 blueprint probes; finished records are retained for 30 minutes. Reads and cleanup return 404 when the probe is missing or its record has expired. A full store of records it must retain returns 503. The counts response contains: This request reads the bot cache, reads the two connection stores, makes one Twin stats call bounded at five seconds, and probes LLM readiness; it does not make an LLM completion call. Probe responses contain: The Eve probe sends one fixed prompt to Eve and is bounded at 90 seconds. The blueprint probe starts the hidden built-in blueprint and is bounded at 300 seconds. Both run under the internal system identity and delete their temporary thread when finished; the blueprint probe also deletes its Mission. The delete route confirms cleanup if artifacts remain and keeps the probe record.

Internal sandbox LLM proxy endpoints

These routes are mounted under /api/internal/sandbox-llm/... and are intended for sandbox Claude Code thread-runner containers, not interactive client use. A per-thread sandbox container calls them with ANTHROPIC_BASE_URL pointed at http://<genesis-host>/api/internal/sandbox-llm (the Anthropic SDK appends /v1/messages or /v1/messages/count_tokens). Genesis validates a thread-scoped session token on each call, injects the real org LLM credentials server-side, and forwards the upstream response — the container never holds the org Anthropic key or other provider credentials.
These routes sit under the /api/internal/ prefix, which is public to AuthRequiredMiddleware (not an Auth0-protected path). Authentication is in-band: the sandbox presents its thread-scoped token as the x-api-key header value. This is not the internal bearer token used by Twin or genesis-scanner routes.
Authentication: Except for the connectivity probe, x-api-key must be a live thread-scoped sandbox session token (minted by Genesis when the container starts). Token records are stored in Genesis metadata, so a backend restart does not invalidate an otherwise-live token; the stored expiry still applies. An invalid token returns 401 with an Anthropic-shaped authentication_error body and the message invalid sandbox session token. A token whose stored expiry has passed returns the same status and error type with the message sandbox session token expired. If the token store is unavailable, the proxy returns 503 with an error response; the proxy service uses an Anthropic-shaped api_error body. The GET or HEAD connectivity probe returns a bare 200 without a token and does not proxy an LLM request. Request body: JSON in Anthropic Messages API shape. When sandbox_runner.llm_model is set (Advanced Settings, genesis-config.yaml, or GENESIS_SANDBOX_RUNNER_LLM_MODEL after the startup config→env bridge), Genesis routes through litellm and overrides the request’s model with the configured litellm model (and, when applicable, the thread’s GUI-selected model tier). The proxy honors the request’s stream flag — Claude Code streams by default; when the flag is omitted, the proxy returns the non-stream plain JSON response specified by the Anthropic API default. Mid-conversation role: "system" entries inside messages are hoisted into the top-level system parameter so Bedrock and other litellm providers accept the request. litellm mode: When sandbox_runner.llm_model is set (for example bedrock/us.anthropic.claude-sonnet-4-6), Genesis serves calls via litellm.anthropic_messages and litellm.token_counter. Streaming responses are SSE already in Anthropic wire format and are forwarded unchanged. Databricks models are also supported: when the configured provider has no Anthropic Messages configuration, Genesis bridges the request to chat completions and re-emits the result in Anthropic format, including tool_use blocks. For Databricks models, Genesis translates stop_sequences to Databricks’ stop parameter before forwarding the request. Token usage is metered per call against (bot_id, thread_id) with usage_category: sandbox_runner. The recorded prompt total includes fresh input plus cache-read and cache-write tokens, while those cache counts remain separate for pricing so cached tokens are not counted twice. A turn served entirely from the cache is still recorded when it has prompt usage. For Databricks models, Genesis restores the first text fragment when a streamed reply starts a new text block after a tool call, so that fragment is not lost from the Claude Code response. If the upstream model rejects extended thinking either in the request parameters or replayed message history, Genesis retries the turn once without the thinking parameter and thinking content blocks. Other provider failures preserve a recognized provider status and the corresponding Anthropic error type, allowing Claude Code to distinguish rate limits and request or credential problems. Failures without a recognized status or rate-limit marker remain 502 with api_error. Direct Anthropic fallback: When sandbox_runner.llm_model is unset, Genesis forwards to https://api.anthropic.com/v1/... using the org Anthropic key resolved from AppConfig (litellm_anthropic provider). Returns 502 when neither litellm mode nor a configured Anthropic key is available. Live GUI streaming: During litellm streaming, Genesis tees the forwarded model SSE into the GUI while the container receives the upstream bytes unchanged. Each visible proxy request (one agent step) uses two identifiers; auxiliary requests that do not advertise tools, such as thread-titling calls, are not painted into the chat reply:
  • message_id — per-step bubble id of the form sbx_{thread_id}_{12-hex}. This identifies the proxy request’s live streaming events.
  • assistant_message_id — turn-level id from the thread’s active completion (when registered). The streamed text deltas, tool boxes, and persisted message rows all carry this id so the chat UI groups them into one assistant reply and reconciles the live copies with the end-of-turn refetch. Omitted when no active completion is registered (best-effort).
  • Assistant text: Genesis emits message_response_delta WebSocket events (made_by: sandbox_runner) with the per-step message_id and, when available, assistant_message_id. full_text contains the complete assistant text accumulated across the turn so far, rather than only the current step; clients should use it as the displayed value for the turn. Each delta also checkpoints this growing full_text on the thread’s active completion so a GUI stop mid-turn preserves the partial reply that was already shown.
  • Tool boxes: When the model SSE includes a completed tool_use block, Genesis emits thread_tool_start with the masked Genesis-native tool name and arguments. When the tool result arrives on a later proxy request, Genesis emits thread_tool_stop once for that tool within the turn, even when earlier results are replayed in subsequent requests. Both events set assistant_message_id to the turn-level id (falling back to the per-step message_id when no completion is registered) so the chat UI renders running/done tool cards live against the same assistant reply (same contract as in-process threads — see Supported progress event types).
Stop during streaming: The GUI stop button bumps the thread’s cancel epoch. While litellm streaming is in progress, the proxy watches that epoch and aborts the upstream generator when it changes — killing the container process does not stop generation because the in-container HTTP call to this proxy stays open. On abort, Genesis opens a brief (10 s) stop window for the thread: any reconnecting sandbox CLI request during that window receives a synthetic empty Anthropic turn SSE instead of regenerating the cut-off step.

Internal Genesis scanner endpoints

These internal routes are mounted under /api/internal/genesis-scanner/... and are intended for Twin migration and scanner automation workflows rather than interactive client use.
All internal Genesis scanner requests require an Authorization header with the internal bearer token: Bearer <internal-token>.

GET /api/internal/genesis-scanner/inventory

This route does not accept query parameters. It returns these top-level fields: Each item in datasets, apps, and schedules includes: For scheduler jobs, this includes system dataset-refresh jobs and the Genesis daily scan job (twin_daily_scan).
During early startup, schedules can be empty and errors.schedules can be present if the scheduler is still starting. Genesis retries the scheduler lookup before returning that partial inventory response.
Each item in sqlite_connections includes:

POST /api/internal/genesis-scanner/scan-sqlite

It returns these top-level fields:
scan-sqlite returns HTTP 400 when ref cannot be resolved to a SQLite file, and HTTP 403 when the resolved file is one of Genesis’s blocked internal databases: genesis.db, rbac.db, datasets.db, genesis_cache.db, scheduler_jobs.db, lineage.db, flows.db, blueprint_runs.db, or debriefs.db.

GET /api/internal/genesis-scanner/harvest-export

This endpoint returns these top-level fields:

GET /api/internal/genesis-scanner/harvest-embeddings

This endpoint returns these top-level fields: Each entry in embeddings includes: SQLite scanner checks reserved database filenames and durable-store paths. The durable metrics.db is blocked by its resolved path rather than by filename; an unrelated user database named metrics.db is not blocked by name alone.

Internal Genesis scanner example requests

Bot settings endpoints

These routes manage per-agent settings and require manage_agents. The settings write route applies the same context soft-limit safety cover used by the Agent Center LLM tab. Settings writes and clears for a version-owned agent return HTTP 409 with error_type protected_core_agent; the version_owned field in the bot list and details responses identifies these agents. The bot-level context soft-limit keys are: When a PUT includes llm.default_soft_limit_tokens, its response data adds requested_soft_limit_tokens, applied_soft_limit_tokens, clamped, clamp_reason, acknowledged_exceeds_hard_limit, hard_limit_resolved, hard_limit_tokens, hard_limit_source, and hard_limit_model_name. An unacknowledged value above the resolved hard limit is persisted as requested but reported as clamped, with the applied value set to that hard limit. Resend the value with llm.default_soft_limit_acknowledge_exceeds_hard: true to override that comparison. If the hard limit cannot be resolved, Genesis does not clamp; hard_limit_resolved is false so clients can distinguish an unchecked value from one that passed comparison. GET /api/bots/{bot_id}/settings exposes the same status under data.soft_limit_status, allowing clients to keep showing a clamp or acknowledged override after a reload. The status is null when the bot has no soft-limit override.

Chat thread list endpoint

Thread metadata is scoped to agents registered in the database. Cross-agent thread scans (search, retention archival, ownership resolution) enumerate active bots from BOT_SERVICING rather than inferring agents from storage directories on disk. At server startup, Genesis preloads a thread-metadata cache for each registered bot so first requests do not pay a cold-load penalty. Orphan storage directories without a matching bot row are not indexed. This route is mounted under /api/bots/{bot_id}/threads.

POST /api/bots/{bot_id}/threads

The response includes thread_id and an initial title. Without title, thread creation opens through the runtime !thread_info turn and clients that open the thread immediately may also receive an assistant welcome message derived from the bot’s bot_intro. With title, no agent turn or LLM call is made during creation; the runtime loads the stored thread when its first agent turn runs.

GET /api/bots/{bot_id}/threads

When auth.enabled is true, the send path also repairs legacy threads whose stored owner_id is system_default_user: the first authenticated non-system caller who resumes the thread becomes the stored owner. After that repair, my_threads_only, GET /api/bots/{bot_id}/threads/{thread_id}/share-info, and owner-scoped thread mutations reflect the repaired owner. When auth.enabled is false, Genesis continues to use system_default_user as the canonical owner.
First-party thread sidebars typically call this route with include_hidden=false. To mirror Genesis’s default sidebar behavior, they can also pass include_handoff_threads=false, which excludes threads whose resolved show_in_thread_list value is false.
When mcp_origin_only=true, Genesis sorts the matching threads by the same stored display timestamp used for each returned row before applying offset and limit. Threads without a stored display timestamp are listed last. This lets the hosted MCP connector page through MCP-opened threads consistently across agents. Returned data[] items include show_in_thread_list (boolean), which tells clients whether Genesis intends that thread to appear in the default sidebar list. Legacy handoff/delegation threads without an explicit stored flag resolve to false for backward compatibility. MCP ask_agent chat items also include mcp_work_thread_id (string or null). For an MCP chat, this is the caller’s work-thread ID, which clients can use to group the chat under its originating work thread in a sidebar. It is unset for other threads and is not parent_thread_id; it does not change the runtime’s parent-thread behavior. Each data[] item’s timestamp field drives sidebar sorting and the “last active” label in first-party clients. Genesis resolves it using this priority chain:
  1. imported_display_timestamp from thread metadata — archived imports (for example, conversations uploaded by the chats-sync daemon) pin the conversation’s true historical date here; it wins over newer last_activity values stamped during import.
  2. last_activity from thread metadata.
  3. timestamp from thread metadata.
  4. Timestamp embedded in thread_id (for example, chat_1234567890_...).
  5. Persisted thread-service props (last_activity, timestamp, or created_at).
  6. Plist file modification time.
  7. Current server time as last resort.
Returned values are ISO-8601 strings with millisecond precision and a Z suffix. Thread search results expose the same resolved value in last_activity. thread_new WebSocket events use the same first three metadata keys (imported_display_timestamp, last_activity, timestamp) so a client that inserts a new sidebar row from the socket shows the same date the thread list would return without a reload.

Chat thread import endpoints

These routes are mounted under /api/bots/{bot_id}/threads/... and restore a thread from an exported package. Prefer import-zip for full ZIP exports that include thread.json, message_log.csv, and file attachments; use import only for legacy JSON-only payloads.
Genesis derives import identity from the cryptographically verified Bearer token on the request (PAT, primary Auth0/OIDC token, or a JWT from an entry in auth.additional_issuers). Values inside the uploaded export — including per-message primary_user in message_log.csv — are never trusted for ownership or access control.A token from an identity_source: email issuer (the OAuth-proxy identity broker) resolves only to an existing user of the deployment’s configured login provider (auth.provider), matched by the token’s verified email — never to users minted by other channels such as Slack. If no such user exists, the request is refused with 403 rather than creating an account: log into the deployment’s web UI once, then retry.During message_log.csv import, Genesis writes each row’s primary_user in MESSAGE_LOG as the verified importer’s identity ({"user_id": "<importer>"}, or null when the request has no authenticated user) and preserves the source CSV value only as display-only provenance in message_metadata.imported_thread_author. That field records who the exported thread said wrote the message; it is not a Genesis user identity and must not be used for authorization.Any sender_provenance value supplied inside an imported thread is removed; imports cannot claim that a message was sent by a verified customer. Imported rows therefore report unrecorded; messages subsequently sent through a server send path receive a new stamp based on that send path.
When the imported thread.json (inside a ZIP or passed as thread_data) includes metadata.source_timestamp, Genesis treats it as the conversation’s true historical date. During materialization Genesis persists it as imported_display_timestamp, which takes priority over last_activity when resolving the sidebar timestamp on GET /api/bots/{bot_id}/threads. Automatic archive importers such as the chats-sync daemon set metadata.source_timestamp so imported threads appear at their original date instead of import time. Manual imports that omit metadata.source_timestamp use the import-time clock and sort to the top of the sidebar like a newly created thread.

POST /api/bots/{bot_id}/threads/import-zip

Send the raw ZIP bytes as the request body with Content-Type: application/zip (or application/octet-stream). When the ZIP contains query_results/results.json, Genesis restores each bundled query result under its original query_id so query-result:// embeds in the imported thread still resolve. Rows whose query_id already exists on the target server are skipped, so re-import stays idempotent. Invalid or oversized payloads are skipped with warnings rather than aborting the whole import.

POST /api/bots/{bot_id}/threads/import

handle_conflict=update continues an existing exported thread in place by appending only messages that are not already stored. Genesis deduplicates using stable message ids: message_id on each thread.json message (also mirrored as metadata.input_uuid) and input_uuid or message_id inside each message_log.csv row’s message_metadata JSON.input_uuid is also how a streamed reply joins its stored rows. Every chat turn has one: the sender’s id when it supplied one, otherwise an id of the form msg_<32 hex> minted when the turn starts. It is the message_id of every message_response_delta WebSocket frame streamed for the turn, the message_id of the turn’s User Prompt row in the thread messages API, and metadata.input_uuid on the turn’s assistant rows. The integer MESSAGE_LOG.message_id column is a storage key only and never appears in a streamed frame.Update mode requires thread ownership (same as replace). Genesis returns 400 when:
  • The remote thread has diverged — it contains messages absent from the import (likely continued inside Genesis). Use replace to overwrite or rename to import as a new thread.
  • The existing thread has messages but none expose stable ids (incremental update is not possible). Use replace or rename.
When every message in the import is already present, the request still succeeds with messages_imported: 0 (a valid no-op). Warnings may note how many new messages were appended.Messages appended by an update import are available in the prompt for the next chat turn on that thread.Every import path sets the thread title from user_title, falling back to ai_title and then Imported Thread when those fields are empty. This also updates the title when handle_conflict=update continues an existing thread. If the title update fails after the messages are stored, the import still succeeds and reports the failure in warnings.
Both import routes return ThreadImportResponse with success, thread_id, original_thread_id, renamed, messages_imported, title, and warnings.

Chat thread access endpoints

These routes are mounted under /api/bots/{bot_id}/threads/{thread_id}/....
All routes below require access to the target agent. When auth.enabled is true, export, fork, and GET /api/bots/{bot_id}/threads/{thread_id}/access follow the standard thread-access check rather than an owner-only check, so public threads, invited threads, callers with view_all_threads, callers who share a role with the owner where that shared role includes view_shared_role_resources, and legacy/system threads whose owner_id is null or system_default_user remain readable. Shared-role visibility widens only this standard access path; routes below that require ownership or manage_all_threads keep those stricter checks. In the auth behavior notes below, references to the thread owner, ownership, or a caller who owns the thread also include this legacy/system-thread fallback for backward compatibility on most thread mutations, including hide, unhide, delete, and access-list updates.
The same thread-access check also applies to thread-scoped message routes under this path prefix: GET /messages, POST /messages, POST /messages/sync, POST /messages/websocket, GET /messages/{message_id}/response, GET /messages/{message_id}/response/delta, and GET /messages/{message_id}/provider-request-export. In auth-enabled deployments, callers must be allowed to access the target thread before sending or polling messages. The same access check also applies to POST /api/bots/{bot_id}/threads/{thread_id}/profile-pre-llm, which profiles live prompt assembly without sending a model request.
In demo mode, when aggregate spend has reached demo.token_budget_usd, POST /messages, POST /messages/sync, and POST /messages/websocket return HTTP 402 Payment Required with error_type = "payment_required" before accepting the turn. The response includes a details object with spent_usd, cap_usd, and cta_url (from demo.sales_url). Outside demo mode, when the cap is unset (null), or when demo.allow_unlimited_budget is true, these routes do not apply this preflight.
For the default GET /api/bots/{bot_id}/threads/{thread_id}/messages path (source=message_cache, which is also used when source is omitted), Genesis can coalesce concurrent reads that use the same bot_id, thread_id, limit, before_timestamp, and include_tool_calls values. This applies to active threads as well, so identical concurrent polls can share one in-flight read instead of triggering duplicate backend work. Genesis also refreshes volatile thread metadata on the returned payload before sending it back, so fields such as title, checklist, assistant_mode, model_tier, model_provider, execution_lane, and is_active stay current even when the underlying message list came from a cached snapshot. Metadata-only updates do not invalidate that cached message list; Genesis rebuilds it only when message content changes or history compression rewrites the thread. Each message also includes the sender_provenance field described below.
On the default source=message_cache path, Genesis strips internal auth/session metadata keys such as _sf_caller_token, _user_context, and _sf_user_info before returning messages[*].metadata. For messages imported from a thread export, metadata.imported_thread_author may be present as display-only provenance copied from the source export’s primary_user column; it is not a Genesis identity and is not used for access control.
For a User Prompt message, sender_provenance identifies which credential sent the message. It is one of customer, agent, delegated, unverified, or unrecorded; customer is the only value that means a verified human customer. agent identifies a server-internal agent channel, delegated identifies a credential acting on a person’s behalf (such as a PAT, app token, sandbox token, machine token, or prompt sent for another user), unverified means no verified person was established, and unrecorded means the server has no recognized stamp (for example, a legacy row). For assistant and other non-user message types, the field is null.REST message sends never classify as customer: even a verified person’s token used through the programmatic REST door is recorded as unverified. WebSocket sends are classified from the authenticated context, or from a server-internal sender classification. Clients cannot set this field through message_metadata. When a stamped prompt waits for an agent slot, Genesis preserves its sender stamp when resubmitting it instead of classifying the resubmission as a new send. If no stamp was supplied, the send path classifies it afresh.
For newly created client-side thread IDs that are still materializing on the backend, GET /api/bots/{bot_id}/threads/{thread_id}/messages can return model_tier: "" instead of defaulting that field to main. Treat the empty string as “no explicit thread tier has been persisted yet.” Once Genesis finishes creating the backend thread record, the response resolves the effective tier normally.
When thread or message payloads include file or artifact links, Genesis returns root-relative URLs on the current Genesis origin. Standalone and local deployments normally prefix these links with /api, while SPCS omits that prefix. Use returned paths as-is. Git-backed attachments resolve through /api/git/... or /git/..., file-manager attachments through /api/files/... or /files/..., and artifact downloads through /api/artifacts/... or /artifacts/.... Git-backed url and download_url fields may also include an optional v={mtime_ns}-{size} query parameter so clients fetch the current file bytes after a same-path overwrite; combine it with download=true when both are present.

Working context endpoints

Working context contains explicit, non-secret selections for a thread’s working location and connections. It grants no additional access; every update is validated against the caller’s current connection grants and repository access.

GET /api/bots/{bot_id}/working-context-options

The response is a WorkingContextOptions object: The connection catalog is scoped to the authenticated caller and agent. The catalog query is cached by the web client for 60 seconds while it is current.

GET /api/bots/{bot_id}/threads/{thread_id}/working-context

Returns the saved working-context object or null when the thread has no saved selection. The object has these fields:

PUT /api/bots/{bot_id}/threads/{thread_id}/working-context

Send the complete working-context object. The server validates that selected connections are still available, that the caller can view the repository, and that the folder exists inside that repository. Repository and folder values cannot target Git internals or traverse outside the repository. A successful response returns the validated and persisted object.

Shared thread path parameters

POST /api/bots/{bot_id}/threads/{thread_id}/export

PUT /api/bots/{bot_id}/threads/{thread_id}/title

It returns these top-level fields:

POST /api/bots/{bot_id}/threads/{thread_id}/fork

It returns these top-level fields:

GET /api/bots/{bot_id}/threads/{thread_id}/fork-info

Returns fork lineage breadcrumbs when the thread was created via POST .../fork. Otherwise returns success: true with data: null. data object fields when present:

DELETE /api/bots/{bot_id}/threads/{thread_id}

This endpoint also supports SQLAlchemy-backed metadata storage, including the Databricks App deployment.

POST /api/bots/{bot_id}/threads/{thread_id}/force-reset

This endpoint does not document any request body fields. Besides clearing in-memory thread markers, Genesis also attempts to cancel queued async prompt work and stop any shared streams for the thread. When state is cleared, Genesis also emits the standard terminal realtime events for the thread so subscribers can recover from a stuck loading or streaming state. It returns these top-level fields:

POST /api/bots/{bot_id}/threads/{thread_id}/profile-pre-llm

Profile live pre-LLM prompt preparation on the loaded thread without calling the model. Genesis clones the thread state, optionally runs one warm-up pass, then measures only prompt-build stages. It returns these top-level fields:

trial item fields

timings_ms and per-metric summary fields

summary repeats the same shape for update_llm_messages_ms, append_system_context_ms, build_runtime_request_snapshot_ms, and total_prep_ms.

cache_flags fields

GET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/provider-request-export

Generate a sanitized provider request export for the selected turn. Genesis reconstructs the payload from stored thread history instead of replaying a captured upstream HTTP request, so data.simulated is always true. Genesis resolves the provider, model, and endpoint from stored message metadata, thread properties, current model assignments, and provider defaults. Returned endpoint_url values are sanitized and never include embedded credentials. When Genesis cannot infer a concrete chat-completions endpoint, the generated curl falls back to https://<AI_PROVIDER_BASE_URL>/chat/completions. When Anthropic-style prompt caching applies, Genesis preserves cache_control markers on the exported messages[*].content[] blocks so replaying the JSON keeps the same cache intent. In checkpoint mode with the default rolling anchor, the deep breakpoint sits on stable history behind the volatile newest user turn (which carries per-turn date/time context). It returns these top-level fields:

GET /api/bots/{bot_id}/threads/{thread_id}/activity

This endpoint does not document any query parameters beyond the shared bot_id and thread_id path fields. It returns these top-level fields:

background_work items

Each item describes one run the agent started and will be notified about when it finishes. Dismissing an item only removes it from the list; it does not stop the work or withdraw the agent’s completion notification.

active_completion fields

active_tool_call fields

active_compression fields

GET /api/bots/{bot_id}/threads/{thread_id}/goal

Returns the goal attached to the thread, including its current status and verdict. The response is a top-level ThreadGoalResponse with success and goal fields; this endpoint has no additional query parameters. When present, revival contains:

POST /api/bots/{bot_id}/threads/{thread_id}/background-work/dismiss

Hide background work from the thread’s waiting card for everyone who views the thread. The work continues running, and the agent is still notified when it finishes. The caller must have access to the agent and be able to see the thread; a caller who cannot see it receives an empty background_work list and does not change the list. The response returns success and data, containing thread_id and the remaining background_work items in the same shape as the activity endpoint.

GET /api/bots/{bot_id}/threads/{thread_id}/model-tier

This endpoint does not document any query parameters beyond the shared bot_id and thread_id path fields. It returns these top-level fields:

PUT /api/bots/{bot_id}/threads/{thread_id}/model-tier

When a thread sets provider to openai while Azure OpenAI is the process-wide default, Genesis routes that turn through the public OpenAI API using saved OpenAI credentials instead of the Azure endpoint. The response reuses the same fields as GET /api/bots/{bot_id}/threads/{thread_id}/model-tier, and adds requested_soft_limit_tokens, applied_soft_limit_tokens, clamped, clamp_reason, and acknowledged_exceeds_hard_limit. These let clients distinguish a requested value from the value persisted after safety clamping and tell the operator why a clamp occurred. A successful acknowledged override is reported explicitly rather than closing silently in the UI. When model_tier changes, Genesis also advances the thread’s context-state cache epoch so the next turn rebuilds cached context metadata under the new tier without requiring a thread reset.

GET /api/bots/{bot_id}/threads/{thread_id}/share-info

This endpoint does not document any query parameters beyond the shared bot_id and thread_id path fields. It returns these top-level fields:

Shared access-list body fields

Debrief endpoints

These routes store and retrieve saved debriefs for chat threads, missions, and Blueprint Journey aggregates. Each debrief record tracks the latest version in current, while earlier versions remain available by version number.
Debrief routes require an authenticated caller. Thread debrief access follows the same thread-visibility rules as chat. Mission debrief access follows the linked mission thread when one exists; if a mission has no bot/thread anchor, Genesis falls back to owner-only access. Journey debriefs are owner-scoped (the chain was filtered for the generating user’s visibility), require agent access when bot_id is set, and also verify thread ownership on source_thread_id when present.

Shared debrief record fields

Shared current-version fields

current and GET /api/debriefs/{debrief_id}/versions/{version_number} return the same version-detail shape.

POST /api/debriefs

POST /api/debriefs returns a DebriefRecord. Thread sources produce the original thread-style payload shape. Mission sources produce the mission-shaped payload described below. Journey sources produce a JourneyDebriefPayload aggregate and may auto-generate missing child mission debriefs for recorded stages before harvesting evidence. With regenerate=true, Genesis regenerates the recorded child mission debriefs from each mission’s current state before assembling the Journey snapshot; unavailable mission data or a child debrief causes the regeneration request to fail. When regenerate is false, Genesis returns the existing current version when the evidence fingerprint is unchanged and synthesis already succeeded (or the journey is still in progress). Equivalent overlapping Journey-generation requests share one in-flight result when their normalized root project, owner, and regenerate value match. Genesis allows up to 8 active generations per root project across distinct keys; a request above that limit returns 429 with Too many Journey generations are already active for this project.

Debrief route parameters

GET /api/threads/{thread_id}/debriefs and GET /api/missions/{mission_id}/debriefs both return debriefs: DebriefRecord[]. GET /api/debriefs/{debrief_id}/versions returns versions, current_version, and total_versions.

Debrief payload kinds

Mission payload sections

Mission debrief payloads are top-to-bottom narrative summaries. These top-level fields can appear on payload when payload.kind="mission".

Journey payload sections

Journey debrief payloads are evidence-first aggregates of a Blueprint chain. These top-level fields appear on payload when payload.kind="journey".

File manager upload endpoint

The file upload route is mounted at /api/files in standalone and local deployments and at /files in SPCS. A normal upload may use the existing thread, bot, or chat form fields. Launchpad pre-start uploads additionally use the blueprint_start_session_id field, which requires an authenticated user, stores the file in a server-owned temporary session directory, and preserves the exact uploaded bytes for checksum verification at Mission start. On a Databricks App with durable uploads, the upload is copied to the bound Unity Catalog volume before it is recorded; uploads larger than files.max_upload_bytes (default 104857600 bytes / 100 MiB) return 413, and a volume write failure returns 503. The size cap does not apply to other deployments. See Durable chat uploads on Databricks for setup and restore behavior.

File upload form fields

File manager file access endpoints

These routes are mounted at /api/files/... in standalone and local deployments and at /files/... in SPCS. On a Databricks App with durable uploads, the download route restores a missing upload from the bound Unity Catalog volume after checking access. The first download after a restart waits for that copy; it returns 404 if the content is no longer available and 503 if the volume cannot be read. See Durable chat uploads on Databricks.

File manager deletion endpoints

These routes are mounted at /api/files/... in standalone and local deployments and at /files/... in SPCS.

File deletion query parameters

Both delete routes return 409 when SimpleFileStorage protects an upload referenced by a sent message and confirm is not true. They return 404 if the file is not found and 500 for an internal error.

File manager markdown conversion endpoints

These routes convert Markdown to downloadable documents. They are mounted at /api/files/... in standalone and local deployments and at /files/... in SPCS. The chat UI’s per-message Save as menu posts completed assistant replies to the PDF, DOCX, and HTML routes; Markdown downloads are built client-side without calling these endpoints. All three routes accept multipart/form-data bodies.

Shared markdown conversion form fields

When PDF or DOCX conversion reads a repository file, the caller also needs view access to the exact repository named by repo_id; see Git permissions.

Sandbox file endpoints

These routes serve files generated in sandbox sessions or stored under the configured downloaded_files root and bot_git/repos. In container-based deployments, Genesis prepares the mounted bot_git workspace on startup and ensures repos/, worktrees/, and .genesis_runtime/ exist before files are served.

Shared sandbox path parameter

Sandbox backend restrictions

The sandbox manager is mounted under /api/sandbox-manager. In Databricks Apps, GET /api/sandbox-manager/backends omits the local backend, and an explicit local start through POST /api/sandbox-manager/sessions/start is refused with HTTP 400. The profile validation route POST /api/sandbox-manager/profiles/validate-start reports the same backend restriction in its validation errors. Use a remote sandbox backend instead.

CLI terminal admin endpoints

These endpoints support the Pulse → CLI Workspaces admin view for Kubernetes-managed direct CLI terminal workspaces. Listing requires VIEW_SANDBOXES; restarting or canceling a workspace requires MANAGE_SANDBOXES. The endpoints return an empty successful listing when the CLI terminal feature is disabled or when the configured runtime does not expose Kubernetes workspace pods.

CLI workspace listing response

The GET response includes live_count, the configured max_workspaces ceiling, whether live metrics were available for the request, and a workspaces array. Each workspace item contains:

Unified realtime WebSocket

Genesis exposes its multiplexed realtime socket at /api/ws/unified_v2. This endpoint carries chat operations plus server-pushed entity events. To receive thread-scoped server events such as thread_new, thread_artifaqt_delta, or agent_action_request, clients send a subscribe control frame on the unified socket. While Genesis is starting, the handshake is refused with HTTP 503 and Retry-After: 5; clients should retry after the indicated delay. If the ASGI server does not support denial responses, Genesis closes the socket with code 1013 instead, which can appear to clients as HTTP 403 because the socket was not accepted. When auth.enabled is true and auth.provider is auth0 or oidc, the client must send a bearer token in the Authorization header or, when auth.allow_query_token is true, in the token query parameter. Connections missing a required token are rejected before the socket is accepted with close code 1008. Genesis verifies the token before using its claims for session ownership: the signature is checked against the issuer’s JWKS, and the expiry, audience, and issuer are validated. Forged or expired tokens therefore fail the ownership check; a verified caller with MANAGE_SANDBOXES can still bypass the session-owner comparison.
For direct chat responses produced after Genesis routes a message to a bot handler, the response data echoes correlation metadata. bot_id echoes the request’s bot target, thread_id echoes the request’s thread target when present, and request_id is echoed exactly as sent when it is a non-empty string. If the client omits request_id or sends "", Genesis generates a request ID and returns it in the response so multiplexed clients can correlate replies.Genesis also copies the original non-empty client request_id onto the terminal message_processing_complete progress event for that turn. If the client omits request_id or sends "", Genesis omits request_id from that server-pushed completion event instead of inventing a new one there.Some message types that answer an earlier server prompt still require a non-empty client-supplied request_id; those message-specific requirements are called out below.Other server-pushed event frames keep their existing payloads, and malformed messages rejected before routing still use the generic error envelope.

Idempotent send_message retries

WebSocket clients may include client_message_id in the data for a send_message frame. Use the same non-empty ID (up to 128 characters) when retrying that message. Genesis scopes IDs to the bot and thread. If the first copy fails while being accepted, a repeat can proceed. When that copy is still pending, Genesis waits up to 10 seconds for its outcome; if it remains pending or was accepted, Genesis suppresses the repeat and sends a message_send_duplicate frame with thread_id and client_message_id in data. Accepted IDs are kept in a per-process, bounded in-memory cache for up to one hour, so this is retry protection rather than durable exactly-once delivery.

Unified WebSocket authentication parameters

Unified WebSocket connection parameters

subscribe control frame

Send a client message with type: "subscribe" and these data fields: Example subscription for thread events from one bot stream:

subscribe_messages control frame

Send a client message with type: "subscribe_messages" and these data fields:
After Genesis accepts a send_message request for a thread, it also attaches that WebSocket connection to the thread’s live message stream. Clients do not need to wait for a separate subscribe_messages round-trip before the server can deliver the accepted reply and its related progress events.
For client-generated chat IDs such as chat_* and litellm_thread_*, Genesis treats an early subscribe_messages request as part of thread materialization rather than an immediate missing-thread error. This lets clients attach the live stream and backfill history while the backend thread record is still being created.
During that same thread-materialization window, the initial message payload delivered on the unified socket can also report model_tier: "" rather than defaulting to main. Treat the empty string as an unset thread override until Genesis has persisted the backend thread metadata.
Repeated subscribe_messages requests for the same bot_id / thread_id pair on one WebSocket connection are idempotent. Genesis returns the existing subscription_id instead of creating a second live subscription, and when the server can reuse the original formatted payload it also reuses data.initial_data.Duplicate subscription_started replies still echo the subscribed thread_id at data.thread_id, so clients should treat that top-level field as canonical for the acknowledgement and use data.initial_data.thread_id only when the nested field is present.

Onboarding approval metadata for send_message

The chat onboarding plan card sends its approval through data.message_metadata.onboarding_start on the unified WebSocket send_message frame. This is conditional metadata for the plan card’s Approve and start action, not a general typed confirmation: Genesis retains this metadata only when the message is the kickoff generated from these picks and the picks pass onboarding validation. Malformed metadata or a kickoff that does not match it is dropped; the message still proceeds as ordinary text. The plan card is the supported way to produce the matching metadata and kickoff. For the user flow and chat tools, see Onboard raw tables and Onboarding intake tools.

ask_user_for_secret event

When a thread-scoped tool call such as get_web_content(action="ask_user_for_secret") needs a stored credential, Genesis emits an event frame where:
  • data.entity_type = "threads"
  • data.entity_id = "<bot_id>"
  • data.event_type = "ask_user_for_secret"
The nested event payload (data.data) uses these fields:

secret_input_response client message

Send a client message with type: "secret_input_response" and these data fields: When Genesis accepts the message, it replies with type: "secret_input_response" and returns the correlated data.request_id, a boolean data.resolved flag, and the standard direct-response correlation fields described above. A resolved: true response means the server found a matching pending request and accepted the reply. Genesis does not auto-generate a replacement request_id for this message type. Blank or omitted values are rejected because the response must match an outstanding ask_user_for_secret request.

secret_input_timeout event

If an ask_user_for_secret request remains unanswered for 600 seconds, Genesis emits data.event_type = "secret_input_timeout" on the same thread stream. The nested event payload (data.data) uses these fields:

Demo budget exhaustion on the unified socket

When demo mode is enabled, demo.allow_unlimited_budget is false, and aggregate spend has reached demo.token_budget_usd, Genesis rejects send_message, replace_last_user_message, and edit_message_and_resume on the unified WebSocket before routing the turn. The check runs only after the agent-access guard succeeds so unauthorized callers do not see aggregate spent_usd / cap_usd figures. Genesis replies with type: "subscription_error", error_code: "demo_budget_exceeded", and message text Demo budget exceeded. Please contact sales to discuss a full deployment. The nested details object includes: For replace_last_user_message, the budget gate runs before any rewind so a blocked replacement does not remove the persisted last user turn. For edit_message_and_resume, the budget gate also runs before any rewind so a blocked edit does not archive thread history.

edit_message_and_resume client message

Send a client message with type: "edit_message_and_resume" to edit any prior user message and re-run the conversation from that point. Unlike replace_last_user_message (in-flight last turn only), this archives every turn from the target node onward — messages are marked discarded, not physically removed — then resends the edited text through the normal pipeline. message_metadata.prover_context is a versioned, immutable subject for a Prover chat. It must contain version: 1, a comparison with diff_hash, source_snapshot_id, target_snapshot_id, delta_hash, and component_pair, and, when the subject is used to stage a package, run_id and pair_fingerprint so the captured evidence can be authenticated. It has a scope with kind (comparison, issue_group, or finding), key, and label, plus a members array. Each member requires review_id, comparison_kind, and a relationship of new, remaining, changed, regressed, or resolved; package staging additionally requires each captured member’s finding_key, positive ledger_id, object_name, and relationship to match the authoritative ledger and review population. Optional column and requirement details may also be included. The array accepts at most 512 members and the complete sanitized object is limited to 256 KiB. Genesis keeps the sanitized Prover subject from the thread’s first user message when later turns provide different metadata, so follow-up requests continue to refer to the original finding group. The subject is supplied to the model as untrusted UI metadata: its fields are identifiers and observations, not instructions. If Genesis cannot sanitize a supplied prover_context, it rejects that value rather than using it to identify a comparison. Resend valid Prover context to preserve the intended subject; the chat does not substitute a comparison based on rejected metadata. When auth is enabled, the caller must own the thread (same bar as forking). Genesis refuses the edit while a tool is still executing (error_code = "tool_calls_active"). On success Genesis replies with type: "message_edited_and_resumed" and a data object that includes: Failures use type: "edit_message_error" with error_code values such as edit_target_not_found, edit_target_not_user_message, tool_calls_active, edit_rewind_failed, or thread_access_denied. On the first blocked message per thread, Genesis also emits an inline agent_action_request with action_type = "demo_interest" and action_data.trigger = "budget_exhausted" so the thread surfaces the Schedule a Demo card (one-shot per thread, shared dedup with the in-flight budget guard). Notifier failures after the budget error is sent do not change the handled block response.

agent_action_request event

When a thread-scoped workflow needs client-side input such as OAuth authorization, a personal-credential binding, or a Try Me demo-interest CTA, Genesis emits an event frame where:
  • data.entity_type = "threads"
  • data.entity_id = "<bot_id>"
  • data.event_type = "agent_action_request"
The nested event payload (data.data) uses these fields: demo_interest action requests currently use these action_data fields: credential_prompt action requests use these action_data fields:
Genesis sends agent_action_request to connected clients on the thread subscriber stream rather than a separately user-targeted stream. This keeps demo and no-auth browser sessions receiving the request even when user-targeted delivery would not match.

agent_action_response client message

Send a client message with type: "agent_action_response" and these data fields: When action_type = "demo_interest", resolved responses may include these result_data fields: When action_type = "demo_interest" and status = "resolved", Genesis uses result_data to update Try Me lead-capture metadata server-side. For action_type = "oauth_connect", clients should still send agent_action_response when they can so the thread UI can acknowledge the result promptly. Genesis also applies a callback-side safety net: once the OAuth callback persists the grant for the same user and saved connection, the server resolves any matching pending OAuth wait even if the popup cannot send the response back to its opener. For action_type = "credential_prompt", the client submits the principal through PUT /api/connections/{connection_type}/{name}/credentials/me rather than sending secret values in the action response. After that bind succeeds, Genesis resolves the matching pending credential wait for the user and connection. Clients can still send agent_action_response to report the card’s resolved, cancelled, or failed state to the thread.

agent_action_response_ack response

When Genesis accepts an agent_action_response, it returns type: "agent_action_response_ack" with these data fields: Successful acknowledgements also include the standard direct-response correlation fields described above. When action_type = "demo_interest", this acknowledgement reflects transport-level acceptance of the client response frame. Try Me lead-capture follow-up runs separately and does not suppress the ack if later server-side processing fails. If request_id is missing or blank, Genesis returns type: "agent_action_response_error" with success = false, error = "request_id is required", and data.error_code = "missing_request_id".

thread_new event

When Genesis creates a thread and notifies subscribers on the threads entity stream, it emits an event frame where:
  • data.entity_type = "threads"
  • data.entity_id = "<bot_id>"
  • data.event_type = "thread_new"
The nested event payload (data.data) uses these fields:

thread_artifaqt_delta event

When the most recent completed tool call changes thread artifacts, or when Genesis needs clients to refresh phase-scoped thread state after that tool completes, it emits an event frame where:
  • data.entity_type = "threads"
  • data.entity_id = "<bot_id>"
  • data.event_type = "thread_artifaqt_delta"
The nested event payload (data.data) uses these fields: artifaqts_delta is phase-scoped. Each entry in artifaqts_delta.phases uses phase_id (string) plus inputs and outputs arrays. For Blueprint and Document Flow threads, Genesis attributes non-blueprint tool outputs to the currently active blueprint phase so clients can group updates by the active phase instead of relying on the fallback "0" phase bucket. For Document Flow replay, the same nested payload can include an optional document_flow_snapshot object. It contains the current artifaqts, optional phase_number and blueprint_name, and messages, a compact list of the Blueprint and coding-agent tool calls and results needed to rebuild coding-agent delegation nodes and their statuses. The snapshot is also emitted when a coding-agent tool starts or stops, allowing a client to show a running delegation before the final artifact update arrives.

Additional thread progress events

Genesis can also emit thread progress event frames for clients that are subscribed to the threads entity stream or already attached to a thread’s live message stream. Thread-scoped delivery keeps Slack-linked and other mirrored thread views receiving progress events even when the broader unified entity subscription is filtered separately. These events use the same outer envelope shape as thread_artifaqt_delta:
  • type = "event"
  • success = true
  • data.entity_type = "threads"
  • data.entity_id = "<bot_id>"
Their nested payload (data.data) always includes these fields:

Supported progress event types

Top-level message_complete frame

Streaming clients should also handle a top-level message_complete frame as the terminal signal for a sandbox turn. Unlike the progress events above, this frame is not wrapped in {"type":"event"}. Its data.content is the definitive assistant text for the turn, including when the live text stream produced no proxy frame before the turn ended.
The message_id and model fields are included only when available. Clients should use content as the final displayed response and stop waiting for the turn when type is message_complete.
A successful POST /api/bots/{bot_id}/threads/{thread_id}/force-reset request that actually clears stuck state also emits both thread_stream_stop and message_processing_complete on the same thread stream. Realtime clients can use those events to unwind stuck loading or streaming indicators without reopening the thread.

Optional progress-event payload fields

When query_result is present, it can include: When event_type = "context_budget_refresh", the nested compression object can additionally include fields such as usage_before_pct, usage_after_pct, reduction_pct, strategy, reduction_scope, reason, phase, progress_message, progress_pct, progress_phase, work_done, work_total, summary_done, summary_total, and parallelism. Treat that payload as a refresh hint and refetch GET /api/bots/{bot_id}/threads/{thread_id}/activity or the relevant context-budget endpoints (/budget for the panel card, /budget-history when a modal is open) for authoritative state.

Log REST endpoints

Genesis exposes operator-focused log routes under /api/logs/.... These endpoints require the access_config_panel permission. GET /api/logs reads from the in-memory Pulse log buffer and returns a formatted historical snapshot. GET /api/logs/download instead streams the configured log file from disk, so it can include log content older than the buffer-backed REST and WebSocket history views.

GET /api/logs/download

Genesis streams the current log file directly from disk. When include_rotated=true, it also appends rotated backups in chronological order, oldest first, separated by file-boundary markers in the response body. Because full log files can be multi-MB, Genesis allows only one active /api/logs/download stream at a time. Concurrent download requests wait for the current stream to finish instead of running in parallel.

GET /api/logs/errors/download

Genesis streams the dedicated error log from disk. This file captures ERROR/CRITICAL records and any log line that carried an exception traceback. Genesis rotates the file daily at midnight; rotated backups use the suffix .<YYYY-MM-DD> (for example errors.log.2026-06-11). When zip=false (default), Genesis streams only the current error log file as plain text. When zip=true, Genesis packs the current file plus all retained daily-rotated backups (oldest first) into a ZIP archive named error_logs.zip. Error-log downloads share the same single-stream concurrency limit as /api/logs/download.

Estate live-progress WebSocket

Genesis exposes the Prover theater live-progress channel at /api/ws/estate/live. The socket streams fine-grained, forward-only progress events for estatechar runs (scan, contract compile, system diff, behavioral validation, fold) so clients can follow a run live instead of polling a coarse run summary. Every event is also persisted on the run row, so GET /api/estate/runs/{run_id} and GET /api/estate/active-runs remain a complete fallback when the channel is disabled or the socket drops. The channel is gated by estate_char.live.enabled (default true). When disabled, the server refuses the connection before accept with close code 1013 and clients should fall back to polling. When auth.enabled is true and auth.provider is auth0 or oidc, the client must send a bearer token in the Authorization header or, when auth.allow_query_token is true, in the token query parameter. Connections missing a required token are rejected before the socket is accepted with close code 1008.

Estate live WebSocket connection parameters

At least one of run_id or pair is required. Connecting with neither, or when the caller’s identity cannot be resolved, is rejected with close code 1008.

Estate live WebSocket close codes (before accept)

Estate live WebSocket server event types

Every server → client message is a flat JSON object with type, run_id (absent only on ping), ts (ISO-8601 UTC), and payload fields merged flat onto the envelope (not nested under data). Events for a single run_id are delivered in emission order. Under backpressure the server drops the oldest queued events (bounded by estate_char.live.buffer_size, default 256); on a detected gap, re-fetch the run via GET /api/estate/runs/{run_id} to resync.

Store-first liveness (headless and API runs)

A run launched outside the backend process (agent, script, or API path — launch_mode: "headless-script" on the run row) publishes its emit() events into its own process’s in-memory hub, which the GUI-serving backend never sees. The WebSocket consumer needs no protocol change: the server reconstructs the same forward-only event stream from the persisted run store so the socket looks identical to an in-backend run.
  • Store-tailing pump — for each followed run_id or pair match, when estate_char.live.store_tail_enabled is true (default), the server polls store.get_run every estate_char.live.store_tail_poll_seconds (default 5) — not the socket keep-alive heartbeat_seconds (default 20) — and synthesizes phase, scan_progress, object_revealed, summary, and done by diffing consecutive summaries. Object names come from the bounded recent_objects buffer in the summary (estate_char.live.recent_objects_buffer, default 25); the exact running count always rides scan_progress. A headless run is tailed from the store immediately. A backend-launched run keeps the pump dormant while in-process hub events are flowing and hands over only after estate_char.live.store_tail_grace_seconds (default 8) of hub silence, so live hub events are never double-delivered. Budget truncation is carried in the summary fields above; the store-tail pump does not synthesize per-object scan_truncated events.
  • Mid-diff verdicts — when estate_char.live.outbox_enabled is true (default), a headless system_diff run buffers family_found and element_verdict events into the estate_char_run_events outbox; one shared backend tail task per followed run (not per connection) polls by watermark every estate_char.live.outbox_tail_poll_seconds (default 2) and republishes new rows into the live hub. Delivery is at-least-once — keep reducers forward-only and idempotent. Backend-launched diffs do not use the outbox (their emit() already reaches the hub in-process).
  • Stale-run protection — the shared estate_char.reconcile.stale_after_seconds window defaults to 2400 seconds. A heartbeat keeps a running row alive, but a heartbeat or completion that arrives after reconciliation has closed the row cannot reopen it; inspect the run summary for any recorded late completion.
Connecting with both ?run_id= and ?pair= for the same run follows it once (one snapshot and one pump).

Estate live WebSocket client messages

The client may grow its subscription on the same socket (for example when a new run starts while the theater is already open):
No acknowledgement is sent and a snapshot is not re-sent for these subscriptions; future deltas for the added run or pair simply start flowing. Any other inbound message is ignored. Typical integration: call POST /api/estate/characterize (optionally after GET /api/estate/discover-schemas to populate the schema picker), open /api/ws/estate/live?run_id=<run_id> with the returned run_id, and drive the theater from object_revealed and phase events. To run a mediated diff, call POST /api/estate/system-diffs and follow the returned run_id or pair_fingerprint on the same socket. To follow a migration pair across characterize → diff → validate runs, connect with ?pair=<pair_fingerprint> and/or send subscribe / subscribe_pair as each new run id appears in the runs feed.

Log streaming WebSocket

Genesis exposes real-time log monitoring at /api/ws/logs. When auth.enabled is true and auth.provider is auth0 or oidc, the client must send a bearer token in the Authorization header or, when auth.allow_query_token is true, in the token query parameter. Connections missing a required token are rejected before the socket is accepted with close code 1008.

Log WebSocket authentication parameters

HTTP request tracing header

Genesis includes an X-Request-Id header on HTTP responses. When the same request produces log entries, that value can also appear as request_id in the live-log context payload, making it easier to trace a failing API call back to the matching Pulse log lines.
Pulse clients typically send subscribe_logs and then get_historical on the same socket. Genesis queues matching live entries that arrive during that bootstrap window and emits each entry once after the historical batch returns, skipping duplicates that were already included in the snapshot.

Log WebSocket message types

Genesis also continues to support get_logs, unsubscribe_logs, get_log_levels, and get_log_statistics on the same socket.

Log filter fields

Genesis accepts both timeRange and time_range in log-socket filter payloads. Context filters are exact-match. module_path is prefix-match. For historical snapshots, get_historical accepts an optional top-level limit field. When omitted it defaults to 2000, and Genesis caps it at 2000. The legacy get_logs message continues to accept filters.limit with the same cap.

Formatted log entry fields

Known live-log context keys

These keys are present in context only when Genesis can resolve them for the current unit of work.

Sandbox terminal WebSocket

Genesis exposes interactive sandbox terminal access at /api/ws/sandbox-terminal/{session_id}. When auth.enabled is true and auth.provider is auth0 or oidc, the client must send a bearer token in the Authorization header or, when auth.allow_query_token is true, in the token query parameter. Connections missing a required token are rejected before the socket is accepted with close code 1008. On Windows servers, the local Docker terminal and Railway terminal are unavailable because they require a POSIX pseudo-terminal; Kubernetes-backed terminal sessions are not affected. For azure_aci sessions, each WebSocket connection opens a new Azure exec session. Genesis uses the dimensions in the panel’s opening resize frame; if no resize frame arrives within two seconds, it opens at 24 rows by 120 columns. Azure Container Instances fixes the terminal size for that exec session, so later resize frames do not change it. When available, persisted terminal history, including commands previously run by the agent, is sent as binary frames before the JSON session_ready frame with the container_id. New shell output follows as binary frames. If opening a terminal fails because the host does not support pseudo-terminals, Genesis sends a JSON session_error frame with reason: "pty_unsupported", final: true, and relaunch: false before closing the socket. Its message starts with Interactive terminals are not available here: and includes the host error. If attaching to the container fails with an OSError (for example, Docker exec cannot start on the host or the container is unreachable), the frame has reason: "container_unreachable", final: true, and relaunch: true, with the message This sandbox's container is not reachable from here. A final error stops the chat terminal panel from automatically reconnecting to that target.

Sandbox terminal connection parameters

Direct CLI terminal WebSocket

Genesis exposes a direct terminal for the caller’s own Claude Code workspace at /api/ws/cli-terminal. The connection is accepted only when cli_terminal.enabled is true. With authentication enabled, the caller must also resolve to a user with the use_cli_terminal permission; the bearer token is read from Authorization, or from the token query parameter when auth.allow_query_token is true. The token may be either a JWT or a Genesis Personal Access Token (PAT) beginning with gp_. JWTs are verified against the issuer’s JWKS and checked for expiry, audience, and issuer. PATs are validated against the credential store and resolved to their owner’s user_id. Both token types go through the same use_cli_terminal permission check. Invalid, forged, expired, revoked, unknown, or owner-unverifiable credentials are rejected before the socket is accepted with close code 1008. With authentication disabled, the feature toggle is the only gate, as the deployment has no caller identity to check. Inside a Databricks App, the configured workspace runtime must be kubernetes; the unset runtime defaults to docker, which the App cannot run. If a client reaches the socket with Docker selected, Genesis accepts the socket, sends an error JSON frame explaining that cli_terminal.runtime=kubernetes is needed, then closes it with code 1008.

CLI terminal connection parameters

After the socket is accepted, text or binary frames are sent to the PTY as terminal input and terminal output is returned as raw bytes. A text frame containing {"type":"resize","cols":N,"rows":N} changes the PTY size. A resize control frame with invalid values is dropped rather than sent to the PTY; all other text frames are treated as terminal input. The workspace uses named tmux sessions, so the Claude Code process can continue when the client disconnects and be reached again by reconnecting with the same session name. Clients that connect with caps=status receive JSON status control frames while the workspace is attaching. The first frame is {"type":"status","message":"Starting your workspace…","capabilities":["status","redraw"]}; while a cold workspace pod is still starting, Genesis sends a liveness frame every 3 seconds with a message such as Waiting for the workspace pod to be ready… (Ns). These frames are sent before the PTY byte stream begins, and older clients that omit caps=status receive none, so they do not print protocol JSON into the terminal. After the client has received the advertised capabilities, it can request an input-free tmux repaint by sending the control frame {"type":"redraw"}. This refreshes the attached pane without sending a keystroke to the running process. The server also accepts this control frame from clients that already know the protocol. A recognized control frame that cannot be acted on is dropped rather than written to the PTY; text that is not recognized as a control frame is treated as terminal input.

Terminal session ended control frame

When the terminal reader stops, Genesis sends each attached client an error control frame and then closes the WebSocket with close code 1011 and reason terminal session ended. The frame has the following shape:
The message explains whether the terminal was closed on the server, the terminal reader stopped, or the workspace running the terminal was restarted or removed. Clients should display the message and let the user reconnect to start a new session. A client that does not handle the control frame still receives the WebSocket close.

Git repository and file endpoints

These endpoints power the Git Browser and Git-backed file delivery APIs. Structured repository operations are mounted under /api/bot-git/v1/.... File and archive delivery routes are mounted under /api/git/... and are also available at /git/... for compatibility links generated elsewhere in Genesis.
Export-related download_url fields, plus chat and thread attachment url/download_url fields for Git-backed files, target these same Git file-serving routes and are returned as root-relative paths on the current origin. In standalone and local deployments they resolve under /api/git/...; in SPCS they resolve under /git/.... Mutable repository files may also carry an optional v query parameter derived from the file’s modification time and size so refreshed payloads bypass stale browser caches after same-path overwrites.
The compatibility /git/... routes are also used by the React Git Browser in SPCS. The same router is mounted at /git/... and /api/git/...; only unprefixed /git/... paths participate in SPA routing. For top-level browser navigations to /git/{repo_id} and non-download /git/{repo_id}/{file_path:path} URLs, Genesis serves the Git Browser SPA shell (index.html) instead of raw file bytes or JSON — including when the path happens to match a repository file. The SPA fetches file content through /api/git/... after it renders, so deep links such as “Open in Git” load the Git Browser instead of a raw 404 or an inline file response.When authentication is enabled, an anonymous browser opening one of these URLs with GET and no Authorization header also receives the SPA shell. The shell contains no repository content; subsequent repository requests still use the normal authentication and Git permission checks. Requests with a rejected Authorization credential are not treated as anonymous page opens.Genesis detects these navigations with the Sec-Fetch-Dest: document header when present. Subresource requests (for example img, iframe, or fetch) that send broad Accept headers are not routed to the SPA shell. Clients that omit Fetch Metadata fall back to the Accept: text/html check.API callers always receive file or JSON responses from /api/git/..., even when they send Accept: text/html. Download requests (?download=true or the /download route) also skip SPA routing on either mount prefix.
When authentication is disabled, Git RBAC checks do not block requests on these routes.
All routes in these Git groups now enforce a system-level Git permission and return 403 Permission denied when the caller lacks that permission. With Git RBAC enabled, direct repository routes also enforce access on the exact repository they serve: read routes require repository view, and write routes require edit or manage where applicable. repo_id must match an existing repository directory exactly; a symlink alias or different casing does not select the repository it points to, and an unknown ID is not treated as a repository.

Git permissions

Shared Git parameters

Read-only Git endpoints

GET /api/git/{repo_id}/archive and GET /api/git/{repo_id}/{folder_path:path}/archive exclude housekeeping paths by default: .archives/, Python caches (__pycache__, *.pyc, *.pyo, *.pyd), OS artifacts (.DS_Store, Thumbs.db), node_modules/, virtual environments (.venv/, venv/), .env and .env.*, .git/, and microapp runtime scratch directories (.microapp_home/, .microapp_state/, .microapp_venv/).

GET /api/git/{repo_id}/{file_path:path}

Serves one repository file inline or as an attachment. The convenience /download route is equivalent to ?download=true.

GET /api/bot-git/v1/repos/{repo_id}/bundle

Returns a Git bundle as application/octet-stream. Omit since to create a full-history bundle, or pass since=<commit-sha> to stream only commits after that SHA. If the requested since SHA already matches the repository HEAD, Genesis returns 304 Not Modified with no response body. Both 200 and 304 responses include X-Bundle-Head so clients can track the current repository head commit across incremental syncs. Genesis rejects shallow repositories before building a bundle because partial history cannot produce a complete cloneable bundle. Re-clone the repository without --depth, or run git fetch --unshallow on the existing clone, before retrying the request.

GET /api/bot-git/v1/{path:path}

When the resolved path is a directory, this route returns a JSON directory listing. When the resolved path is a file, it streams the file content instead.
Directory listings include dot-prefixed files and folders (for example .env, .gitignore, and .cursor). Genesis omits Git metadata from listings. Direct requests into .git paths through this Git Browser route return 403 with Access denied: .git directory is off-limits. Git-backed file-serving routes do not serve Git metadata, and mutating repository routes reject paths into it, including paths reached through symlinks. ZIP archive downloads always exclude Git metadata (see the archive endpoints note above).

Mutating Git endpoints

POST /api/bot-git/v1/repos

Creates a repository under the Git Browser root. The request body accepts these fields: The response includes the effective repo_type read from the repository after creation. A 400 response is returned when repo_type is not storage or git, or when a git repository is requested for a repository ID reserved as storage-only. Existing repository IDs return 409.

POST /api/bot-git/v1/repos/{repo_id}/upload-batch

Uploads multiple files as a multipart form. The endpoint requires manage_git and repository edit access. Managed repositories accept writes only when the resolved view is the caller’s WIP view; use source=wip or the corresponding managed-view parameters when selecting that view. The response contains results, in file order. Each result includes the requested path, a status of uploaded, conflict, skipped, or failed, and optional saved_path and error fields. When successful files are uploaded to a managed WIP view, commit_hash contains the single local commit created for the batch; commit_error is populated when the files were saved but that commit failed. If any file has a collision with the default error choice, the batch returns conflict results without publishing the batch; retry with an explicit collision choice for each conflict.

Git example requests

Bot management endpoints

These routes are mounted under /api/bots/....
Genesis refuses agent lifecycle changes that would leave no active agent holding manage_agents. Affected routes return HTTP 409 with error_type last_agents_manager and an error message naming the surviving manager(s) and advising you to grant manage_agents to another active agent first. Attempting to delete built-in Adam or Eve returns HTTP 409 with error_type protected_core_agent instead of soft-deleting them.
Requests that change a version-owned agent definition, including instruction reset, return HTTP 409 with error_type protected_core_agent.

GET /api/bots

By default, Genesis returns only active agents the caller can access. When include_disabled=false (the default), Genesis filters out DISABLED agents before applying per-agent access checks. The response returns success plus a data array. Fields relevant to list and deactivation-aware UIs include:

GET /api/bots/instruction-blocks/manifest

Returns success plus a grouped manifest of toggleable instruction blocks that the agent edit UI can render without hardcoding block names. Each block entry contains:

GET /api/bots/deactivated-retention

This admin-only endpoint returns one retention summary per deactivated source agent, ordered newest first by updated_at.

Shared bot path parameter

GET /api/bots/{bot_id}

Returns success plus a data object. Fields relevant to description and instruction editing include: If a bot record has no stored instruction_render_mode, Genesis normalizes the response to structured.

GET /api/bots/{bot_id}/clone-starter

This endpoint does not document any request body or query parameters beyond the shared bot_id path field. It returns success plus a data object with: For Eve and Adam, data.clone_starter_instructions is the built-in default persona plus the built-in dynamic instruction block bodies, joined with blank lines in their normal order. For other bots, Genesis returns the stored bot_instructions value after removing any internal [CUSTOM] storage marker.

GET /api/bots/{bot_id}/summary

This endpoint does not document any request body or query fields. It returns success plus a data object with these top-level fields: data.hero.role_statement is populated from bot_intro only. When no description is stored, the field can be null.

POST /api/bots

The caller needs manage_agents or create_agents. A caller with only create_agents receives management rights on the agent created by this request, but cannot edit unrelated agents unless separately granted access. Description- and instruction-related request fields include: On success Genesis returns success, message, and the created bot_id.

PUT /api/bots/{bot_id}

When status changes, Genesis applies a lifecycle transition rather than updating only the database row:
  • DISABLED — Genesis persists the disabled status, evicts the agent’s live runtime sessions, and rejects new work routed to that agent.
  • ACTIVE — Genesis persists the active status, rebuilds a usable runtime session before returning success, and returns the agent to normal routing. If runtime startup fails (for example missing LLM configuration), Genesis rolls the status back to DISABLED and returns an error; the agent remains deactivated until reactivation succeeds.
Work already admitted before a deactivation may finish; Genesis does not forcibly cancel an in-progress LLM or tool call. On create and update, bot_instruction_blocks_disabled entries must match the manifest name values returned by GET /api/bots/instruction-blocks/manifest. Genesis only keeps optional block names that apply to the target bot: shared optional blocks for custom agents, plus the matching Eve- or Adam-specific optional blocks for those built-in agents.

DELETE /api/bots/{bot_id}

This endpoint uses the shared bot_id path field plus these body fields: This is a soft-delete. Genesis sets the agent status to DISABLED and then applies the requested thread-retention behavior.
  • With thread_policy="keep_with_agent", Genesis leaves thread files and metadata on the deactivated agent, removes those threads from the thread-search index, and records a deactivated-retention summary.
  • With thread_policy="move_to_agent", Genesis requires an active destination agent other than the source bot_id, moves thread files and metadata to that agent, reindexes them there, and records the resulting retention summary.
On success, this endpoint returns success and message.

POST /api/bots/{bot_id}/reset-instructions

This endpoint does not accept a request body. On success Genesis returns success and message. For Eve and Adam specifically:
  • In structured mode, reset restores the default persona text and keeps the current bot_instruction_blocks_disabled selections.
  • On the legacy editing path, reset restores the default persona text, switches instruction_render_mode back to structured, and clears the disabled-block list.

GET /api/bots/{bot_id}/_debug/rendered-instructions

This diagnostic route requires manage_agents and returns 403 unless ENABLE_BOT_INSTRUCTIONS_DEBUG=true is set before startup. Each block_breakdown row contains:

Context Control endpoints

These routes are mounted under /api/context-control/.... They back Agent Center capability controls and the Thread Details model/context drawer, and they require agent access to the target bot_id.
GET /api/context-control/capabilities/{bot_id}/catalog reflects AgentSkills imports and deletions immediately after a successful POST /api/agent-skills/import or DELETE /api/agent-skills/{skill_name} call. Clients do not need to wait for the capability-registry reconcile interval before refreshing the catalog.
DELETE /api/context-control/threads/{bot_id}/{thread_id}/active does not accept a request body. On success Genesis returns success: true plus data.cleared to indicate whether any thread-scoped context state was removed.

Shared context patch body fields

These fields are accepted by the preview/apply/defaults patch routes:
Policy-disallowed tool groups or skills now return 400 with structured details instead of being silently stripped from the patch. Bot-default updates also return 400 when active_tool_groups would auto-load more than 12 tool groups by default. Operators can raise or lower that auto-load cap with CONTEXT_DEFAULTS_MAX_TOOL_GROUPS. There is no default cap on the total number of tool functions those groups contain — the provider function cap is the only ceiling, and it is enforced at thread preflight (409).
Thread preview/apply requests that name a policy-authorized channel-bound tool group whose runtime adapter cannot dispatch also return 400. Their structured details include unavailable_tool_groups, mapping the group name to the runtime reason; this is distinct from policy_rejected_tool_groups. For the corresponding persistent bot-default behavior, see Data Agent Creation Tools.
Tool-group policy checks (including tool_access_mode="all") validate against the loadable universe: the capability catalog union the live runtime tool-group registry. MCP-backed groups that register after the startup catalog reconcile (for example lineage_tools) remain activatable and assignable to defaults without spurious not_in_policy rejections while the catalog snapshot is still catching up. After validation, tool_access_list and active_tool_groups are persisted exactly — including explicit empty allowlists or denylists — even when the searchable capability index is temporarily empty or stale. Partial bot-default patches that omit policy fields leave existing tool_access_mode, tool_access_list, and active_tool_groups unchanged.
Agents calling the set_bot_context_defaults or set_bot_capability_policy tools in Data Agent Creation Tools receive the same structured rejection payload in the tool response details field (for example rejected_skills and access-mode context), not only the collapsed error string returned by the HTTP middleware for dashboard clients.
When a thread preflight exceeds the provider function cap, the 409 response’s details includes provider_limit, active_function_count, over_by, and a remediation object. remediation.message gives the recovery guidance; suggested_groups_to_disable lists optional groups with tool_group, function_count, and just_requested; required_by_active_skills lists groups required by active skills with tool_group, function_count, skills, and just_requested_skills. When previous state is available, just_requested marks a newly active group and just_requested_skills names newly active skills that require the group. Required groups are not offered for removal. If the always-on and skill-required functions alone exceed the limit, the suggestions are empty and the message explains why removing optional groups cannot help.

Context budget read endpoints

These routes do not accept request bodies. On success each returns success: true plus a data object. /budget and /budget-details resolve data.context_limit_tokens through the same pipeline so both endpoints always report the same limit for a thread. Genesis resolves the limit in order: the live trimmer when the thread is in-process (its soft limit already folds in per-request, per-thread, per-bot, and environment overrides), the thread’s persisted soft_limit_tokens property when set, the resolved model’s hard context limit, or — only when all three steps fail — a conservative 200000 default. data.context_limit_source records which step produced the limit:

GET /api/context-control/threads/{bot_id}/{thread_id}/budget

Returns the always-polled context-budget payload for the Thread Details panel. To keep large or heavily compressed threads responsive, Genesis omits heavy modal-only arrays from this response:
  • data.compression_history carries summary fields (count, total_event_count, visible_event_count, latest_event, average_reduction_pct) but not the per-event list. count matches visible_event_count (rows returned). total_event_count is the monotonic lifetime compression count for the thread (from persisted compression_history_event_count when set).
  • data.thread_cost_summary carries thread cost totals but not pricing_details. estimated_cost_usd is known-priced cost: calls without a known price add nothing, so when unpriced_call_count is greater than zero, render the cost as partial beside that count. The summary also includes pricing_details_available (boolean, default false) to signal whether GET .../budget-history will return per-model pricing rows, and unavailable (boolean, default false), which is true when a dependency required to determine the totals failed; in that case the numeric fields are schema-default zeros and clients must render them as unknown rather than as a zero cost.
Other notable data fields include bot_id, thread_id, source, context_limit_tokens, context_limit_source, current_usage_pct, current_total_tokens, usage buckets such as system_prompt and conversation, compression_history, projected_next_call, last_actual_call, and thread_cost_summary totals (input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cache_total_tokens, cache_rate_pct, estimated_cost_usd, unpriced_call_count). The payload also includes reactive_effective_ceiling_tokens, the ceiling targeted by reactive compression after applying the resolved hard limit and configured or default soft limit, plus reactive_effective_ceiling_source identifying how that ceiling was resolved.

GET /api/context-control/threads/{bot_id}/{thread_id}/budget-history

Returns modal-only history payloads. Clients should call this lazily when opening the Pricing Used for Estimated Cost or compression-history modals, not on every panel refresh.

GET /api/context-control/threads/{bot_id}/{thread_id}/budget-details

Returns request-breakdown buckets for the request-details modal (for example system_prompt, tool_specifications_effective, tool_calls_and_results, and related entry counts). This route is separate from /budget-history and is not trimmed for modal lazy loading. data.context_limit_tokens and data.context_limit_source use the same resolution pipeline as /budget (see the table above). It also returns data.reactive_effective_ceiling_tokens and data.reactive_effective_ceiling_source, which expose the ceiling actually targeted by reactive compression after the hard and configured or default soft limits are resolved.

Document index management endpoints

These routes are mounted under /api/indexes/... and require the manage_document_indexes permission.

Shared document-index path parameters

DELETE /api/indexes/{index_name}

This endpoint does not document any request body fields. It returns these top-level fields:

GET /api/indexes/{index_name}/delete-impact

This endpoint does not document any request body or query fields. It returns success plus a data object with these fields:

GET /api/indexes/operations/{operation_id}

This endpoint does not document any request body or query fields. It returns these top-level fields:

Additional admin endpoints

These routes live outside the Blueprint and Git groups but are part of common RBAC, agent-import, and app-import flows.

User avatars

POST /api/avatars/upload uploads the signed-in user’s PNG, JPG, JPEG, GIF, or WebP image (maximum 2 MB). DELETE /api/avatars removes that user’s avatar; GET /api/avatars/{user_id} serves an image or returns 404 when none exists, and GET /api/avatars/{user_id}/info reports whether one is present. On Databricks Apps with durable storage enabled, uploaded avatars survive host replacement. See Deploying Genesis on Databricks — Durable SQLite stores. If durable storage cannot record avatar deletion during DELETE /api/rbac/users/{user_id}, Genesis returns 503 and does not delete the user; retry the request. Configuration profile names used with /api/config/profiles/{name} must be plain file names: they cannot start with ., or contain /, \, or ...

GET /api/rbac/users response fields

The response contains success and a users array. Each user item includes the following identity and role fields:

POST /api/admin/export-state

Streams application/x-tar with Cache-Control: no-store, private. The archive is full-fidelity (no secret scrubbing) and contains metadata.json, a consistent SQLite backup as genesis.db, and directory trees bot_storage/ and bot_git/. Repository .git directories are stored under _dotgit_ arcnames (the golden-image build renames them back). Apply a downloaded archive locally with uv run import-state while the Genesis server is stopped. When packing bot_storage/ and bot_git/, Genesis omits regenerable microapp build artifacts: any member whose path includes a .microapp_venv, .microapp_home, or __pycache__ directory component is dropped, pruning the whole subtree. Authored source next to those directories is still included; virtualenvs are rebuilt on provision or first app start. For a workspace export that can optionally include packed virtualenvs, use POST /api/admin/export-microapps with include_venv=true. The export also omits the top-level bot_storage/upgrade-summaries/ directory. It contains the exporting installation’s post-upgrade-advisor baseline and summaries, which should not be carried into a golden image and treated as the new installation’s own upgrade state. When durable GenesisApp state is available, the export restores each registered app’s kept state into its local workspace before building the archive. An app whose workspace is not on disk, whose runtime is active, or whose state cannot be restored is not restored by this step; a restore failure is logged and does not prevent the export. Restoring state can add catalog checks and one volume download per state file to the export time.
This endpoint remains the full-fidelity tar export for the golden-image pipeline. For incremental snapshots in the Helm chart’s PVC layout, use the uv run export-state CLI described in Incremental object-store snapshots.

POST /api/admin/state-snapshots

Queues a full-fidelity, incremental snapshot of this instance to an s3:// or gs:// destination and returns HTTP 202 with success: true and a job record. The request validates the destination and master key before it queues the job. For SQLite metadata, it also checks the available space needed for the database copy. Snowflake-mode instances have no local database for that check; Genesis converts their Snowflake metadata to genesis.db while the exporter runs. If that conversion fails, the job fails after the server has stopped and the instance starts again. After the response is sent, Genesis stops the server, runs the exporter while the state is quiescent, and starts the server again. The server may therefore be unavailable briefly while the snapshot runs; poll the job instead of retrying the request. See State Snapshots and Migration for the Snowflake migration workflow and conversion report. The caller must hold all seven permissions: access_config_panel, view_agents, view_users, view_connections, manage_secrets, manage_all_connections, and manage_scheduler. The confirm value must equal this instance’s name. Example request:
While the exporter owns the container, GET /api/healthcheck and GET /api/health/live remain available. GET /api/admin/state-snapshots/current returns unauthenticated progress for the running job; other requests receive 503 with Retry-After: 30. Once the server restarts, use the authenticated job endpoints below for the destination, transfer statistics, snapshot ID, object count, byte count and any error. Completed snapshots can be restored into an empty Helm PVC with the chart’s restore.from setting; see Restoring an object-store snapshot for the deployment configuration and verification behavior.

GET /api/admin/state-snapshots

Returns success: true with a jobs array, newest first. Each job has a status of pending, running, succeeded, failed, or aborted, plus its request and completion timestamps, current phase, transfer statistics, and result fields when available. A job can include warning when Genesis cannot confirm that no stored secrets remain under a bound previous master key. The snapshot still completes, but restoring it requires that key to be bound too; wait until re-sealing leaves no secrets under the previous key and take a new snapshot to avoid that requirement. Genesis retains the newest 50 finished job records.

GET /api/admin/state-snapshots/{job_id}

Returns success: true with one job record using the same fields as the list response. A missing job ID returns 404.

POST /api/admin/environment-snapshot/export

Returns a timestamped application/zip download named genesis-environment-snapshot-<UTC timestamp>.zip. The archive is assembled in memory and is not written to deployment storage. Genesis also returns X-Genesis-Snapshot-Export-Id, a unique identifier for the export, and marks the response Cache-Control: no-store, private with Pragma: no-cache. The caller must hold all four permissions: access_config_panel, view_agents, view_users, and view_connections. The endpoint does not broaden access to any of the domains it inventories. The ZIP contains this fixed file set: The export contains no credentials, secret values or raw secret references; no conversation, mission, SQL, tool, log, attachment or database content; and no email addresses, usernames or session identifiers. It can include operator-chosen skill, agent, connection, role and blueprint names, hostnames, account identifiers, and other non-secret configuration values, so treat it as customer-confidential diagnostic material rather than an anonymized export or a backup. A section that is unavailable is represented with available: false and a reason; a collection failure aborts the export instead of producing a partial archive.
GET /api/rbac/roles surfaces the same top-level workspace permissions used by the authenticated UI. Role payloads can therefore include permissions such as view_apps, view_context_graph, view_deployments, view_flows, view_scheduler, and view_sandboxes, which drive primary workspace visibility when authentication is enabled. Role payloads can also include opt-in permissions such as view_shared_role_resources, which lets members of the same team-style role see each other’s missions and apps without manual invitations; view_all_blueprints and manage_all_blueprints, which grant blueprint-wide access without per-blueprint grants; and the category-scoped PAT permissions (access_pat_connections_databases, access_pat_connections_git, access_pat_connections_cloud, access_pat_connections_pipelines, access_pat_connections_tools), which control PAT-class saved connection modes such as pat, api_token, and api_key for connectors in each category. Built-in roles do not include view_shared_role_resources by default.GET /api/rbac/permissions returns success plus a permissions array. Each permissions[] item includes name (string), description (string | null), and extended_description (string | null). The short description is what the Config → Users & Roles permissions panel renders under each permission name; extended_description, when present, is shown as a hover or keyboard-focus tooltip via a ? icon next to the short line. Permissions without a server-side description are displayed in the UI as readable text derived from the permission name. No RBAC permission is required beyond authentication.GET /api/rbac/microapps returns success plus a microapps array. Each microapps[] item includes app_id (string), title (string), published (boolean), created_by (string | null), and tags (string[]). Genesis normalizes tags to non-empty strings so API clients can offer tag-based bulk selection without additional cleanup.

Current user permissions

When Access Preview is active for an admin caller, roles and permissions reflect the request-scoped preview role set (from the UI or X-Genesis-Access-Preview-Roles), and is_admin is false because the built-in admin role cannot be included in preview. user_id always returns the caller’s persisted identity. For the built-in user role’s source-contract grant, see the RBAC in Genesis → User.

One-on-one and agent-memory endpoints

These routes back onboarding check-ins, weekly one-on-one cadence, and the per-agent memory documents surfaced in chat. Checklist topic_key values are workspace-configured stable identifiers. Read them from GET /api/rbac/me/one-on-one-status before updating progress; unknown keys are rejected with an error that lists the valid required or optional keys. Default required topic_key values are name_and_language, job_role, data_sources, and validated_data_access. Default optional topic_key values are collaboration_preferences, short_term_objectives, and broader_goals. Workspace admins can customize these lists in Workspace Policies; agents in chat advance checklist progress through the user_memory_action tool (see user_memory_action_tools), which Genesis auto-admits during active onboarding or one-on-one threads.

RBAC read endpoints

These admin read routes require manage_users. Agent-keyed path identifiers must be the exact, case-sensitive bot_id (see Agent identifier validation below). The one-time startup backfill for Adam access on legacy built-in user roles is described in the RBAC Practical Guide.

Agent-management RBAC write endpoints

These endpoints manage per-agent editing grants and creator ownership. They do not grant chat access to an agent.

Agent connection profile write endpoint

POST /api/rbac/agents/{bot_id}/connection-profile

Returns AgentConnectionProfileResponse with bot_id, role_name, granted, revoked, defaults_set, removed_roles, protected_roles_still_granting, and role_created. Revoking a profile role from an agent uses the existing DELETE /api/rbac/bots/{bot_id}/roles/{role_name} route (or the agent-editor UI, which calls it when you uncheck a profile and click Apply). Assigning a profile role uses POST /api/rbac/bots/{bot_id}/roles. Both write routes validate bot_id the same way as the read routes below.

Agent identifier validation

Agent-keyed RBAC routes require the exact, case-sensitive bot_id as returned by GET /api/bots. Display names (bot_name) are not identifiers — passing a display name or a case variant of a known bot_id returns 404. UI-created agents store a uniqueness suffix on bot_id (for example, display name Cary → Cary-k7m2x9); use that full id in RBAC URLs and API calls, not the display name alone. When the agent is not found, Genesis can include a Did you mean: ...? suggestion in the error detail when it finds close bot_id matches (for example, a wrong-case input). Routes that validate the agent path parameter:

Shared RBAC read path parameters

Global RBAC tool-policy endpoints

These admin routes require manage_users and back the system-wide tool-family controls shown in Workspace Policies → Tool Permissions. Workspace policy read and write routes also support SQLAlchemy metadata databases, alongside the legacy database connector.

Shared global tool-policy response fields

The GET list endpoints return these top-level fields: Each tool entry in tools includes:

Shared global tool-policy write field

Both PUT /api/rbac/high-risk-tools/disabled and PUT /api/rbac/coding-agent-tools/disabled accept the same request body field:

Admin audit log

Mutating RBAC routes (user/role lifecycle, role assignment matrices, bot-role mappings, workspace policy toggles under /api/rbac/settings/..., /api/rbac/high-risk-tools/..., and /api/rbac/coding-agent-tools/...) and advanced-config write routes (PUT /api/config/advanced/value, PUT /api/config/advanced/values, and reset endpoints) append best-effort rows to the ADMIN_AUDIT_LOG metadata table. Revoking a role from an agent (DELETE /api/rbac/bots/{bot_id}/roles/{role_name}), revoking a role from a user or agent principal (DELETE /api/rbac/users/{user_id}/roles/{role_name}), removing manage_agents from a custom role (PUT /api/rbac/roles/{role_name}), or deleting a role (DELETE /api/rbac/roles/{role_name}) can also return HTTP 409 with error_type last_agents_manager when the change would leave no other active agent holding manage_agents. Grant the permission to another active agent first, then repeat the change. Each row includes action, actor_user_id, actor_email (when the caller is a human admin), target_type, target_id, JSON details, source (typically api for REST), and timestamp. Advanced-config events record the config path and value type only — never the submitted value. Admins can read these rows through the dashboard (Config → Admin Audit Log) or the read-only endpoints below. Both require access_config_panel. For action names, source taxonomy, column definitions, and example SQL, see RBAC Practical Guide → Admin audit log.

GET /api/admin/audit-log

Returns success: true with data containing: Each items[] entry includes:

GET /api/admin/audit-log/{audit_id}

Returns success: true with data.item using the same fields as one items[] entry from the list endpoint.

Durable storage restore proof

These admin-only endpoints report what declared state came back after a restart and confirm deletes that the catch-all storage sweep refused. The report is a read-only status check; it does not set the server’s lasting degraded status. When durable storage is disabled (including deployments off the Databricks App), the report returns enabled: false with verdict not_enabled and performs no I/O. A proof is run at most once at a time, reused for 15 seconds, and may issue up to four Lakebase catalog queries plus one lstat per kept catalog row. While a previous master key is bound, it also streams every live durable-tree manifest, issuing a catalog query per page of rows.

GET /api/admin/durability/report

Returns success: true with data containing: The report compares the durable catalog and restore records with this instance’s state roots. A missing item is kept remotely, expected on this disk, but absent locally; lazy entries, pending entries, and whiteouts are not missing. For process-relative checks, this process begins at the start of its boot restore; when that timestamp is unavailable, its process start time is used. This keeps a previous process’s shutdown flush from being treated as state from this process during an overlapping redeploy. lost_known identifies state recorded as lost, while conflict means a local copy displaced restored bytes. secret_refused reports a secret by name only. A root with unconfirmed refused deletes is degraded; confirming it does not mark those deletes as missing. The proof also checks live durable-tree catalog rows against this process’s restore outcomes, and checks listed git-durable repositories for a restored .git directory. A failed tree restore is reported as lost_known; a tree whose restore has not yet been recorded is pending, or lazy_absent when it is lazily restored. For a listed repository with a valid path, an absent .git directory is pending while git restores are due and missing otherwise; an invalid path is missing. Git declarations are marked not_checked when git durability is off.

POST /api/admin/durability/confirm-deletes

Confirm only the refused delete set currently represented by the report’s refusal value. If the refused set changes, reload the report and confirm its current value. The next full sweep walk records the confirmed deletes. Returns success: true with data.root and data.confirmed_deletes. The confirmation is audited with the acting admin’s identity.

POST /api/bots/import/validate

The caller needs manage_agents or create_agents. Validation does not create an agent; the permission controls whether the caller may use the import flow.

POST /api/apps/import/validate

Response fields (in addition to success, valid, app_id, id_collision, suggested_id, errors):

POST /api/apps/import

Import response can include unresolved_dependencies for slots still unsatisfied after import.

POST /api/apps/{app_id}/fork

Requires CREATE_APP. Caller must have manage on the source app. Returns 409 with ARTIFACT_IS_GIT_BACKED when the source is not git-backed, APP_NOT_FOUND when missing, or TARGET_NAME_EXISTS / NAME_COLLIDES_WITH_GIT_BACKED when new_name collides.

POST /api/apps/{app_id}/export-to-managed/preview

Requires manage_git and app-level manage. Returns a preview of the standalone app bundle that export-to-managed/commit would copy (excludes local state paths such as __pycache__, venv dirs, and dotfiles). Response includes success, files (each with path, size_bytes, is_text), and warnings. On failure, error is set.

POST /api/apps/{app_id}/export-to-managed/commit

Requires manage_git and app-level manage. Returns 400 when both source_id and repo_id are missing. On success, includes success, source_id, repo_id, and pull-request metadata (pr_url and/or nested pull_request with pr_url, pr_number).

POST /api/flows/{flow_id}/fork

Requires manage on the source flow. Returns 404 (FLOW_NOT_FOUND), 409 (ARTIFACT_IS_GIT_BACKED when source is not git-backed, NAME_COLLIDES_WITH_GIT_BACKED, or TARGET_NAME_EXISTS), or 400 for other failures. Git-backed flows and apps cannot use Genesis versioning endpoints (enable, promote, revert, discard, and related routes) until forked. PUT updates that only change is_enabled (pause/resume) remain allowed on git-backed flows.

AgentSkills self-add request review

These request-review routes require manage_skills: If a durable request record has malformed metadata, package content, or file paths, these routes return 400 with an error detail. Genesis validates the full record before restoring any package files.

AgentSkills package file endpoints

These routes are mounted under /api/agent-skills/... and back the Skills library package viewer for bundled files such as SKILL.md, examples, or supplementary docs.

Shared AgentSkills package file parameters

GET /api/agent-skills/{skill_name}/files/{relative_path:path} returns success: true plus data with the same pagination metadata used by the Skills viewer, including continuation offsets when the file is truncated. The /raw variant returns text/plain directly instead of a JSON envelope.

Agent rooms endpoints

These bearer-authenticated routes let separate development agents share a room through one Genesis server. Rooms are public to authenticated callers who can see them: joining and reading do not create a second access-control model. The caller’s participant name is derived from the authenticated email local part, or the user ID when no email is available; clients do not provide it. Configure the agent that owns room threads with rooms.bot_id before creating the first room.

Room request and response fields

DELETE /api/rooms/{room_id} closes the room and removes it from the room listing. Closed rooms stop answering, but their underlying thread is retained for operator access. This is an archive, not a deletion. GET /api/rooms and room creation return you alongside the room data. A message send returns message_id, recipients, and truncated, plus a status of appended when the message is already in the transcript or buffered while a turn is running. A read returns messages and tail_node; store that node ID and pass it as after on the next poll. Each roster entry contains name, attached, and optional last_seen presence data. Room messages addressed to the room’s agent are drained into the room thread by the background room sweep rather than the ordinary mailbox sweep. Direct chat writes to a room thread are rejected; send through these room endpoints so room attribution, ordering, truncation, and rate limits are preserved. Each participant can send up to 30 messages to one room in any rolling 300-second window. The limit is per sender and room, regardless of recipient; exceeding it refuses the send without recording it. Only messages successfully enqueued consume the allowance. The in-memory window resets when the server restarts.

Hosted MCP gateway endpoints

These routes expose the hosted remote MCP connectors plus the setup and readiness surfaces shown in Config → Agent Gateways. GET /api/config/mcp/cortex-code-skill returns application/zip with a Content-Disposition filename of genesis-cortex-code-skill.zip. When authentication is enabled, any signed-in user may download it. Requests for /.well-known or /.well-known/... paths that do not match a registered route return 404; they do not fall through to the React SPA. /api/config/mcp/readiness returns success: true plus data with mcp_url, protected_resource_metadata_url, authorization_server_metadata_url, auth_mode, auth, readiness, setup, copy, and notes. data.auth_mode identifies the hosted-MCP auth posture. It returns snowflake_spcs for Snowflake Native App / SPCS ingress, disabled when auth is off, header_proxy when auth.provider is proxy and no auth.issuer is configured, and otherwise the configured auth provider name (or unknown). data.auth also includes the optional client_id field. Genesis resolves that value from auth.client_id, auth0.client_id, AUTH0_CLIENT_ID, or AUTH_CLIENT_ID. The data.external block also includes the normalized forwarding inputs Genesis used for URL construction: forwarded_proto, forwarded_host, forwarded_port, configured_public_base_url, and using_configured_public_url. data.readiness now includes critical_failures, warnings, and per-check severity fields in checks[]. Each readiness check includes id, label, severity, status, message, and remediation. Readiness status values are pass, warn, fail, and not_applicable. When proxy headers are incomplete, Genesis can fall back to one of GENESIS_MCP_PUBLIC_URL, GENESIS_PUBLIC_URL, GENESIS_PUBLIC_BASE_URL, GENESIS_EXTERNAL_URL, SERVICE_BASE_URL, NGROK_BASE_URL, or FRONTEND_BASE_URL instead of deriving a localhost URL. In a Databricks App, Genesis ignores forwarded request headers and does not use NGROK_BASE_URL or FRONTEND_BASE_URL to construct MCP OAuth URLs. The MCP specific overrides remain first; Genesis then checks GENESIS_PUBLIC_BASE_URL, SERVICE_BASE_URL, DATABRICKS_APP_URL, and (on AWS commercial workspaces) the runtime-derived App URL. If none is available, OAuth metadata and URL-bearing WWW-Authenticate challenges return 503; other MCP requests do not require a public URL. See Deploying Genesis on Databricks — Public URL and OAuth callbacks. data.setup is the deployment-specific client setup payload used by Config → Agent Gateways. It includes deployment_kind, headline, recommended_path, claude_code, claude_desktop, cursor, cortex_code, test_prompts, and diagnostics_summary. It can also include network_exposure and enable_sign_in. data.setup.deployment_kind returns snowflake_spcs, hosted_oauth, header_proxy, auth_disabled, or unknown. Each of claude_code, claude_desktop, cursor and cortex_code includes title, summary, steps[], notes[] and, optionally, more[]. Each steps[] item is one numbered step with title, instruction, an optional command (label and value, the one copyable value for that step) and an optional one-line hint. Each more[] item is a section the page shows collapsed below the steps, with id, title, summary and commands[]; id admin_setup carries material only an admin acts on, such as the OAuth redirect URI to register. For hosted OAuth, Claude Code’s steps are Add, Sign in once (interactive claude, then /mcp) and Try it (the claude -p smoke test, which cannot sign in by itself). test_prompts includes interactive and command_line. When deployment_kind is auth_disabled, network_exposure is loopback for a loopback public base URL or network otherwise. The Agent Gateways page labels these states Local only and Sign-in off, respectively, and both provide no-credential setup commands. For the auth_disabled setup, enable_sign_in contains the same setup-section shape plus docs_path, which points to /docs/deployments/Hosted-MCP-For-Claude-Code#turn-on-sign-in. It is omitted when the authentication mode is unknown. data.setup.diagnostics_summary includes status, failure_count, warning_count, hidden_check_count, primary_blocker, and blockers[]. Each blocker uses id, label, message, and remediation. For hosted OAuth/OIDC deployments outside Snowflake SPCS, Genesis adds a client_id_configured blocker when no OAuth client ID is available for browser-based Claude setup instructions; headless PAT authentication remains available. That blocker is not raised in header_proxy mode, which has no OAuth client by design. When no client ID is configured, the setup payload omits the Client ID command and the --client-id flag rather than emitting a placeholder value, and the Claude Code and Cursor steps use a personal access token instead. When the redirect bridge runs with dynamic client registration, no client needs a client ID: the Claude Code command has no --client-id/--callback-port flags, the Cursor entry is just {"url": ...}, and Claude Desktop shows no Client ID. The hosted Claude Code Try it step carries a one-line hint on sending a PAT as Authorization: Bearer <token> for scripts and CI. For the default v2 connector, data.copy.coworker_invite, data.copy.first_prompt, and data.setup.test_prompts.interactive guide callers through call_tool(name="list_agents") and ask_agent(...) instead of the legacy status / help onboarding flow. data.copy.coworker_invite is worded for the deployment: browser sign-in on hosted OAuth, a personal access token on API-key deployments, the user’s own Snowflake credentials on Snowflake SPCS, and no credential when sign-in is off. Every deployment’s setup payload carries a data.setup.cortex_code section for Snowflake Cortex Code (cortex mcp add ... --type http). On Snowflake SPCS it adds --snowflake-connection <snow_cli_profile>, so Cortex Code signs each request with that Snow CLI connection’s Snowflake session. On hosted OAuth with the redirect bridge and dynamic client registration it signs in through the browser; on other authenticated deployments it sends a personal access token as -H "Authorization: Bearer <gp_...>"; with sign-in off it uses the bare URL. On Snowflake SPCS, the first (Add) step of data.setup.claude_code, data.setup.claude_desktop and data.setup.cursor carries a variants array: one entry per way the bridge signs in to Snowflake, in the order snow_cli_profile then private_key. The step’s own command stays set to the snow_cli_profile variant’s command, so a client that ignores variants still gets a working command. Each variant has id, label, instruction and command, and the private-key variant adds a hint. The profile variant passes --snow-connection <snow_cli_profile>; the private-key variant passes --auth private-key --account <org-account> --user <snowflake-user> --role <snowflake-role> --private-key-file /path/to/rsa_key.p8. Claude Code’s variants are claude mcp add commands; Claude Desktop’s and Cursor’s are mcpServers config entries. The second (Check) step of those three tabs also carries variants, keyed to the same credentials. Each variant has id, label, an empty instruction, and command; unlike Add, Check variants do not include the private-key hint. Each runs npx -y <bridge> test with that variant’s credential flags, and its own command is the snow_cli_profile check. The dashboard shows one credential switch per tab, on the first step with variants; later steps follow that choice. Every variant pins the same published bridge version (@genesiscomputing/[email protected]). No other deployment kind emits variants. /api/config/mcp/config returns success: true plus data.default_agent_id (string, default "") and data.default_agent_unavailable_policy (require_selection, first_available, or "" when not set; default ""), which are the in-app settings. It also returns data.effective_default_agent and data.effective_default_agent_unavailable_policy, what the MCP server actually uses: each has value, source (in_app, deployment_setting, default, or per_caller when no single default is set and Genesis picks per caller — Eve, then Adam, then the caller’s first accessible agent) and setting_name (the environment variable, such as GENESIS_MCP_DEFAULT_AGENT_ID, when source is deployment_setting). An in-app value overrides the deployment setting; clearing it falls back to the deployment setting again. PUT /api/config/mcp/config accepts the two in-app fields and returns the same payload. Genesis trims default_agent_id, and an invalid unavailable-policy value normalizes to "" (not set), so GENESIS_MCP_DEFAULT_AGENT_UNAVAILABLE_POLICY applies, and require_selection when that is unset too. When auth.provider is snowflake, /api/config/mcp/readiness reports data.auth_mode as snowflake_spcs. In that mode, readiness checks that do not apply to Snowflake SPCS ingress return not_applicable, including issuer_configured and audience_configured, and on Snowflake public ingress hosts the forwarded_headers and configured_public_url checks can also be not_applicable. When auth.provider is proxy and no auth.issuer is set, data.auth_mode and data.setup.deployment_kind are header_proxy. An authenticating ingress proxy signs users in and forwards the identity in a header, so Genesis holds no OIDC issuer: issuer_configured and audience_configured return not_applicable rather than failing, and the setup instructions use a Genesis personal access token. Setting auth.issuer (with auth.audience, auth.jwks_url and auth.client_id) opts the deployment back into browser OAuth, and the OIDC readiness checks apply again. For Microsoft Entra ID, Genesis recognises both issuer families and maps each to the endpoints of its own token version: https://login.microsoftonline.com/{tenant}/v2.0 to the v2.0 endpoints, and https://sts.windows.net/{tenant}/ to the v1.0 endpoints. Every other issuer keeps the default {issuer}/authorize and {issuer}/oauth/token shape. When GENESIS_OAUTH_PROXY_ENABLED is truthy and auth.issuer is configured, Genesis advertises itself in protected-resource metadata (authorization_servers points at the Genesis public base URL) and as the issuer in authorization-server metadata. authorization_endpoint is {base}/authorize, token_endpoint is {base}/oauth/token, jwks_uri and userinfo_endpoint still reference the upstream issuer, and client_id_metadata_document_supported is true. When the OAuth proxy is disabled, /authorize and /oauth/token return 404 and well-known metadata delegates to the configured upstream issuer (OPRM-style). When auth is enabled but no auth.issuer is configured, the deployment has no OAuth authorization server to advertise. This is the expected posture for Snowflake SPCS and header-proxy ingress: the ingress authenticates the request before Genesis receives it, so the protected-resource document remains available but omits authorization_servers and scopes_supported, while both authorization-server discovery routes return 404. Clients should use the Genesis personal access token described by the setup payload instead of starting an OAuth authorization-code flow. /api/config/mcp/smoke returns success: true plus data with mcp_url, aggregate status, and individual checks[] entries for protected-resource metadata, authorization-server metadata, the bearer-auth challenge, and the optional authenticated gateway status probe. When auth is disabled for local development, the auth_challenge check returns not_run instead of fail. When hosted identity delegation is unavailable, the authenticated_gateway_status check returns not_run with the message Gateway status was not run: hosted identity delegation is unavailable. rather than fail. The smoke response still returns success: true; its aggregate data.status is not_run unless another check has status fail. When auth.provider is snowflake, the hosted /mcp/ transport can use Sf-Context-Current-User-Token as the caller credential when no Authorization bearer is present. Genesis preserves that Snowflake current-user token in the hosted MCP request context for downstream gateway calls. Hosted assertions are used for application admission and are not forwarded as the caller bearer token to downstream gateway calls; the verified user context is used instead. Hosted-MCP artifacts[].download_url values point at /api/mcp/artifacts/{artifact_id}?token=... instead of browser-cookie-bound /git/... links. Genesis only signs image, JSON, PDF, CSV, and plain-text uploads for this route, and each link is rejected after it expires or is used once.

Export download responses

Several Genesis API routes stream export packages as binary downloads rather than JSON. On 200 responses, Genesis sets an RFC 6266 / RFC 5987 Content-Disposition header with two filename parameters: This dual-parameter format prevents export requests from failing when a display name contains Unicode. Routes that use this header today include: Typical attachment filenames:
  • Agent export: agent_{name}_{YYYY-MM-DD}.zip
  • Blueprint export: {blueprint_name}_export.zip
  • AgentSkills export: {skill_name}_export.zip
  • Mission export: mission_{name}_{YYYY-MM-DD}.zip
  • Thread export: derived from bot id, thread id, date, and format extension (for example .zip, .pdf, or .md)
Repository-backed ZIP exports omit .git metadata, including nested metadata directories and files reached through links; the archive contains working files, not Git commit history. See System Manager export tools.

Thread Export

POST /api/bots/{bot_id}/threads/{thread_id}/export

Export a thread as Markdown, PDF, or ZIP. The endpoint returns a binary file download rather than a JSON response body. ZIP exports also include scrub_manifest.json. For this public export endpoint, Genesis preserves original thread content by default, records tool_output_redaction: "none", and the manifest reports status: disabled, so the ZIP remains suitable for import. Support-bundle workflows build the same thread_export.zip shape but rewrite role: "tool" output content in thread.json and message_log.csv to schema-only descriptors by default, preserving columns, row counts, and value types without including raw values. The thread-export manifest records tool_output_redaction, tool_outputs_redacted, tool_output_redaction_fallback_drop_count, and tool_outputs_redacted_in_message_log_csv; binary attachments remain unchanged. The ZIP metadata.json records message_log_source for the rows used to create message_log.csv: message_log means the rows came from the MESSAGE_LOG table, while thread_store means Genesis synthesized them from the durable thread store. The synthesized form can contain placeholders for columns that the durable store does not provide. ZIP exports may also include query_results/results.json with cached query-result payloads keyed by their original query_id. Genesis records whether this file was bundled in metadata.json under contains.query_results. Results are included up to the threads.export_max_query_results_bytes cap (default: 25000000; env: THREAD_EXPORT_MAX_QUERY_RESULTS_BYTES). Oversized individual results are skipped rather than dropping the whole bundle. Support-bundle exports never include raw query data.
Thread exports follow the same thread access rules used elsewhere in Genesis. The authenticated caller can export only threads they are allowed to access, including owned threads, public threads, explicitly shared threads, threads allowed by view_all_threads, threads visible through a shared role carrying view_shared_role_resources, and legacy/system threads whose owner_id is null or system_default_user.

Responses