API
Base path /api/v1; the OpenAPI document is served at /api/v1/openapi.json and the interactive
browser at /api/v1/docs. Program routes live under /programs/{program_id}/... (constant
PROGRAM_PREFIX in app/routers/_scope.py), organisation routes under /orgs/current/.... The API is
designed: 117 routes are mounted and answer with the contract’s DTOs, connection providers and tools
are stubs, and the agent runtime is a fake unless AGENT_RUNTIME_URL is set. Design target host
api.trovensa.com; local http://localhost:8100.
Conventions
- Every list route returns
Page[T](items[], total, limit, offset) withlimit1 to 200 (default 50) andoffsetfrom 0. - Every
*Outcarriesid,created_at,updated_at; scoped rows carryorganization_id,program_id,stage_id; agent-capable rows carryorigin,agent_run_id,illustrative. - Exactly one
require_permission(...)per route (the RBAC matrix test fails on any unlisted mounted route). Health routes are public;/me,/permissions,/personasand/providersneed only a valid token. - Every mutation writes an audit row
<target>.<verb>in the same transaction (app/services/audit.py::write_audit); the worker writes no audit rows. - Request bodies are capped by
BodySizeLimitMiddleware; protocols, reports and SOPs get the larger document limit. - Success codes:
201on create,202when a run is queued (sync,agent-runs,checks,invocations),204on delete, otherwise200.
Errors: problem+json
Errors are RFC 7807 documents with media type application/problem+json. type is
https://console.trovensa.com/errors/<slug> (for example .../forbidden), and every document carries the
request_id that the X-Request-ID response header also exposes. A 403 adds permission; a 422 adds
errors[] with loc, msg, type.
{
"type": "https://console.trovensa.com/errors/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "this action requires permission evidence.hypotheses.approve",
"request_id": "6f1c0d0e-illustrative",
"permission": "evidence.hypotheses.approve"
}Without a token every non-health route answers 401 with a WWW-Authenticate: Bearer challenge.
Identity and the development mode
Production tokens are RS256 from the identity provider with issuer and audience pinned by AUTH_ISSUER,
AUTH_AUDIENCE and AUTH_JWKS_URL. Development and tests use HS256 tokens with header kid: dev signed
with DEV_TOKEN_SECRET; they are accepted only when APP_ENV is dev or test, and
validate_deployed_secrets() refuses to start a deployed environment that still carries the placeholder
secret. Mint one with:
uv run python scripts/dev_token.py --user owner@example.com --org trovensa-demo --roles org_owner,program_ownerRoutes
117 designed routes, grouped by router in mount order (ROUTERS in app/main.py). The legacy router
(410 responses for retired paths) is mounted last and is empty.
Router health (2)
/healthpublic, no token/health/readypublic, no tokenRouter me (1)
/meany signed-in userRouter permissions (1)
/permissionsany signed-in userRouter personas (1)
/personasany signed-in userRouter providers (1)
/providersany signed-in userRouter orgs (14)
/orgs/current/orgs/current/orgs/current/programs/orgs/current/programs201/orgs/current/programs/{program_id}/orgs/current/users/orgs/current/groups/orgs/current/groups201/orgs/current/groups/{group_id}/orgs/current/groups/{group_id}204/orgs/current/role-assignments/orgs/current/role-assignments201/orgs/current/role-assignments/{assignment_id}204/orgs/current/auditRouter programs (7)
/programs/{program_id}/programs/{program_id}/programs/{program_id}/members/programs/{program_id}/role-assignments/programs/{program_id}/role-assignments201/programs/{program_id}/role-assignments/{assignment_id}204/programs/{program_id}/auditRouter connections (6)
/programs/{program_id}/connections/programs/{program_id}/connections201/programs/{program_id}/connections/{connection_id}/programs/{program_id}/connections/{connection_id}204/programs/{program_id}/connections/{connection_id}/sync202/programs/{program_id}/connections/{connection_id}/runsRouter agent_runs (4)
/programs/{program_id}/agent-runs/programs/{program_id}/agent-runs/{run_id}/programs/{program_id}/agent-runs/{run_id}/events/programs/{program_id}/agent-runs/{run_id}/cancelRouter evidence (22)
/programs/{program_id}/evidence/questions/programs/{program_id}/evidence/questions201/programs/{program_id}/evidence/questions/{question_id}/programs/{program_id}/evidence/questions/{question_id}/programs/{program_id}/evidence/questions/{question_id}204/programs/{program_id}/evidence/sources/programs/{program_id}/evidence/sources201/programs/{program_id}/evidence/sources/{source_id}/programs/{program_id}/evidence/sources/{source_id}/programs/{program_id}/evidence/sources/{source_id}/links201/programs/{program_id}/evidence/findings/programs/{program_id}/evidence/findings201/programs/{program_id}/evidence/findings/{finding_id}/programs/{program_id}/evidence/findings/{finding_id}/programs/{program_id}/evidence/hypotheses/programs/{program_id}/evidence/hypotheses201/programs/{program_id}/evidence/hypotheses/{hypothesis_id}/programs/{program_id}/evidence/hypotheses/{hypothesis_id}/programs/{program_id}/evidence/hypotheses/{hypothesis_id}/decision/programs/{program_id}/evidence/gaps/programs/{program_id}/evidence/gaps/{gap_id}/programs/{program_id}/evidence/agent-runs202Router plans (26)
/programs/{program_id}/plans/plans/programs/{program_id}/plans/plans201/programs/{program_id}/plans/plans/{plan_id}/programs/{program_id}/plans/plans/{plan_id}/programs/{program_id}/plans/plans/{plan_id}/transition/programs/{program_id}/plans/plans/{plan_id}/approve/programs/{program_id}/plans/protocols/programs/{program_id}/plans/protocols201/programs/{program_id}/plans/protocols/{protocol_id}/programs/{program_id}/plans/protocols/{protocol_id}/programs/{program_id}/plans/protocols/{protocol_id}/transition/programs/{program_id}/plans/protocols/{protocol_id}/approve/programs/{program_id}/plans/experiments/programs/{program_id}/plans/experiments201/programs/{program_id}/plans/experiments/{experiment_id}/programs/{program_id}/plans/experiments/{experiment_id}/programs/{program_id}/plans/reports/programs/{program_id}/plans/reports201/programs/{program_id}/plans/reports/{report_id}/programs/{program_id}/plans/reports/{report_id}/programs/{program_id}/plans/reports/{report_id}/transition/programs/{program_id}/plans/reports/{report_id}/approve/programs/{program_id}/plans/tools/programs/{program_id}/plans/tools/{tool_id}/invocations202/programs/{program_id}/plans/tools/invocations/{invocation_id}/programs/{program_id}/plans/agent-runs202Router review (16)
/programs/{program_id}/review/sops/programs/{program_id}/review/sops201/programs/{program_id}/review/sops/{sop_id}/programs/{program_id}/review/sops/{sop_id}/programs/{program_id}/review/sops/{sop_id}/approve/programs/{program_id}/review/checks/programs/{program_id}/review/checks202/programs/{program_id}/review/checks/{check_id}/programs/{program_id}/review/findings/programs/{program_id}/review/findings/{finding_id}/programs/{program_id}/review/findings/{finding_id}/programs/{program_id}/review/findings/{finding_id}/decision/programs/{program_id}/review/deviations/programs/{program_id}/review/deviations201/programs/{program_id}/review/deviations/{deviation_id}/programs/{program_id}/review/deviations/{deviation_id}Router progress (16)
/programs/{program_id}/progress/overview/programs/{program_id}/progress/milestones/programs/{program_id}/progress/milestones201/programs/{program_id}/progress/milestones/{milestone_id}/programs/{program_id}/progress/milestones/{milestone_id}/programs/{program_id}/progress/milestones/{milestone_id}204/programs/{program_id}/progress/dependencies/programs/{program_id}/progress/dependencies201/programs/{program_id}/progress/dependencies/{dependency_id}204/programs/{program_id}/progress/open-items/programs/{program_id}/progress/open-items201/programs/{program_id}/progress/open-items/{item_id}/programs/{program_id}/progress/approvals/programs/{program_id}/progress/approvals201/programs/{program_id}/progress/approvals/{approval_id}/withdraw/programs/{program_id}/progress/approvals/{approval_id}/decisionProviders
GET /providers returns the four connection providers of app/integrations/registry.py, every one
designed:
| id | name | category | auth method | scheduled sync |
|---|---|---|---|---|
literature_search | Literature search | literature | api_key | yes |
eln | Electronic lab notebook | eln | oauth2 | yes |
document_library | Document library | documents | oauth2 | yes |
analysis_tools | Analytical and design tools | tools | api_key | no |
Agent runs
POST .../evidence/agent-runs (kind in evidence_compare, evidence_gaps, evidence_hypotheses with
a question_id), POST .../plans/agent-runs (plans_draft_protocol, plans_draft_report with a
plan_id) and POST .../review/checks (review_check) create an AgentRunOut in status queued. The
worker claims it, drives the runtime through bootstrap, start, status, submit, and writes the
result as new records with origin = agent: findings and hypotheses with an assertion_kind, drafts in
status draft, review findings with decision open. Public error codes on failure: runtime_unavailable,
session_lost, budget_exhausted, timed_out, cancelled_by_user, attribution_leak,
internal_error.
Source: api/app/main.py, api/app/routers/_scope.py, api/app/routers/health.py, api/app/routers/me.py, api/app/routers/permissions.py, api/app/routers/personas.py, api/app/routers/providers.py, api/app/routers/orgs.py, api/app/routers/programs.py, api/app/routers/connections.py, api/app/routers/agent_runs.py, api/app/routers/evidence.py, api/app/routers/plans.py, api/app/routers/review.py, api/app/routers/progress.py, api/app/errors.py, api/app/config.py, api/app/integrations/registry.py, api/app/services/agent_runtime.py, api/scripts/designed-routes.json, api/README.md