API Endpoint Reference
Everything the web UI does goes through the same HTTP API the CLI and the AI chat tools use, so you can script RootCoz from CI, from a shell, or from your own tooling. This page groups the endpoints by what they do, states the minimum role each one needs, and points at the live OpenAPI schema for exact field-level detail.
Prerequisites
- A running RootCoz server. The examples use
http://localhost:8000; see Quickstart. - An API key. Register or log in in the web UI, or send
Authorization: Bearer <API_KEY>with any request. - The role your call needs:
viewer,reviewer,operator, oradmin. See Managing Users and Server Settings.
Quick Example
curl -H "Authorization: Bearer $ROOTCOZ_API_KEY" \
http://localhost:8000/results/JOB_ID
That single authenticated GET returns the stored analysis for one job, including failures, classifications, patterns, classification overrides, tracker links, capabilities, and child jobs. It does not include the comment thread — call GET /results/{job_id}/comments separately for that.
Step-by-Step
- Authenticate.
Send your API key as a bearer token. The rootcoz_session cookie set at login works too, and X-Forwarded-User works when the server runs behind a trusted proxy with TRUST_PROXY_HEADERS=true.
bash
curl -H "Authorization: Bearer $ROOTCOZ_API_KEY" \
http://localhost:8000/api/auth/me
GET /api/auth/me is the cheapest way to confirm your identity, role, and reports access. Send OPTIONS requests freely — CORS preflight bypasses authentication everywhere.
- Discover the schema instead of guessing field names.
| Path | What it is |
|---|---|
GET /openapi.json |
Full OpenAPI schema, public |
GET /docs |
Swagger UI, public |
GET /redoc |
ReDoc UI, public |
These three are public so an integrator can read the contract without credentials. Every route in the schema sets an explicit camelCase operation_id derived from the handler name, so generated clients get stable method names such as getJobResult and analyzeSubmittedJob.
Note: This page is a curated map of the endpoints people actually call. The live schema at
/openapi.jsonis the exhaustive list.
- Know the role ladder before you script anything.
| Role | Can do |
|---|---|
viewer |
Read jobs, results, tests, history, dashboards, reports when granted |
reviewer |
Everything a viewer can, plus comments, chat, review state, classification and pattern overrides, tags, tracker links, personal tokens |
operator |
Everything a reviewer can, plus submit analyses, re-analyze, abort, delete own jobs, push to exporters |
admin |
Everything an operator can, plus delete any job, manage users and roles, server settings, token usage, log stream, admin chat |
can_view_reports is a separate flag from role. Admins always have it; anyone else needs an admin to grant it. Most calls that create or modify data also run against the ALLOWED_USERS allow list, which is empty (open) by default and always bypassed for admins. The check is per-handler, not global, so it is enforced on data-modifying routes rather than as a middleware boundary; tests/test_allow_list_coverage.py enumerates the mutating routes and fails if one that can write data is added without the check. The exemptions are listed exactly in that test: the authentication routes, the read-only Jira and token-validation lookups, the metadata rules preview, and DELETE /api/user/ai-credentials/{provider} — revoking your own key stays possible even if you are off the list, otherwise being removed would leave you holding a credential you cannot remove.
Warning: A role you do not hold returns
403, not404.403 Admin access required,403 Reviewer access required, and403 Operator access requiredname the exact gate you hit.
Public and unauthenticated routes
| Method | Path | Notes |
|---|---|---|
| GET | /health |
Liveness, returns immediately |
| GET | /api/health |
Detailed health including database and AI sidecar |
| GET | /metrics |
Prometheus metrics |
| GET | /api/releases/latest |
Proxies GitHub release metadata (version, changelog) |
| GET | /openapi.json, /docs, /redoc |
Schema and interactive API browsers |
| POST | /api/auth/register |
Create a user, returns the raw API key once |
| POST | /api/auth/login |
Exchange username + API key for a session cookie |
| GET | /api/auth/needs-key |
Browser-only identity probe |
| GET | /api/auth/pending-status |
Poll while an account awaits approval |
Authentication and your account
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /api/version |
viewer | Running version |
| POST | /api/auth/logout |
authenticated | Clears the session cookie |
| GET | /api/auth/me |
viewer | Username, role, can_view_reports, can_use_server_providers |
| POST | /api/auth/rotate-key |
viewer | Rotates your own API key; the old key stops working. Known gap — see the note below |
| GET | /api/user/ai-credentials |
viewer | Supported provider IDs with a configured flag each. Keys are never returned |
| PUT | /api/user/ai-credentials/{provider} |
reviewer | Store a personal key. Body is {api_key, model} — model is required (400 without it, since the key is verified against it) and the key is only persisted when the provider returns accepted. A rejected or inconclusive check is not an HTTP error: it returns 200 with ok: false, an outcome, and an explanatory message, leaving any existing credential unchanged, so clients must check ok rather than the status code. {provider} is an exact provider ID from GET /api/user/ai-credentials (for example anthropic or google), not the claude/gemini/cursor names used for analysis settings. Unknown IDs are rejected with 400 |
| DELETE | /api/user/ai-credentials/{provider} |
reviewer | Remove a personal provider key |
| GET | /api/user/tokens |
viewer | Stored Jira and GitHub tracker tokens |
| PUT | /api/user/tokens |
reviewer | Save Jira and GitHub tracker tokens. Each field is independent: a string sets it, "" or null clears just that field, and an omitted field keeps its stored value, so clearing one credential leaves the others intact. A body with none of the three fields writes nothing and returns saved: false; anything other than a string or null is rejected with 400 |
| POST | /api/validate-token |
viewer | Validate a GitHub or Jira token before saving |
| POST | /api/jira-projects |
viewer | List Jira projects visible to the supplied credentials |
| POST | /api/jira-security-levels |
viewer | List Jira security levels for a project |
Registration returns the raw API key exactly once and sends Cache-Control: no-store. Store it before you navigate away.
Submitting and driving analyses
All of these continue in the background and return 202 Accepted, except POST /results/{job_id}/abort, which returns 200 OK. POST /submit ingests only and stops before cloning or AI work; POST /analyze runs the full pipeline.
| Method | Path | Min role | Body highlights |
|---|---|---|---|
| POST | /analyze |
operator | type (jenkins, file, raw, prow) plus source-specific fields |
| POST | /submit |
operator | Same body; stops at ingest, analysis_state=submitted |
| POST | /results/{job_id}/analyze |
operator | Runs the AI pipeline on a submitted job; optional ReAnalyzeRequest |
| POST | /re-analyze/{job_id} |
operator | Re-runs analysis with a new ReAnalyzeRequest |
| POST | /api/failures/{failure_uuid}/re-analyze |
operator | Re-analyzes one failure in place, keeps the previous answer |
| POST | /results/{job_id}/abort |
reviewer | Cancels a running analysis |
The shared body accepts the server defaults as per-request overrides:
| Field | Purpose |
|---|---|
type |
jenkins, file, raw, or prow |
job_name, build_number |
Jenkins job and build |
raw_xml, failures, passed_tests, skipped_tests |
File and raw sources |
prow_job_name, build_id, gcs_prefix |
Prow build identity and artifact prefix |
prow_url, gcs_bucket |
Prow server defaults, overridable per request |
ai_provider, ai_model, force_server_credentials |
claude, gemini, or cursor |
ai_call_timeout, max_concurrent_ai_calls |
AI budget for this run |
peer_ai_configs, peer_analysis_max_rounds |
Multi-AI debate settings |
additional_repos |
Extra repositories to clone for context |
tests_repo_url, tests_repo_token |
Tests repository used for history and context |
jira_url, jira_email, jira_api_token, jira_pat, jira_project_key, jira_ssl_verify, jira_max_results, enable_jira |
Jira search during analysis |
github_token |
GitHub access for comment enrichment |
raw_prompt, issue_prompt |
Additive prompt instructions |
labels, plus name and tag fields |
Metadata for the job |
Note: Resolution order for provider, model, timeout, concurrency, peers, and additional repos is request →
.rootcoz/settings.jsonin the cloned tests repo → server setting → environment variable. See Configuring Analysis Context.
Reading results
| Method | Path | Min role | Query parameters |
|---|---|---|---|
| GET | /results |
viewer | limit (max 100), analysis_state (submitted, analyzed) |
| GET | /results/{job_id} |
viewer | fields — comma-separated allowlisted paths |
| GET | /api/results/fields |
viewer | none; returns the allowlist |
| GET | /api/results/{job_id}/tests |
viewer | status (repeatable: passed, skipped, failed), child_job_name, child_build_number, offset, limit (1–200, default 50) |
| GET | /api/failures/{failure_uuid} |
viewer | none |
| GET | /results/{job_id}/review-status |
viewer | none |
| GET | /api/dashboard |
viewer | limit, offset |
| GET | /api/dashboard/filtered |
viewer | search, status, date_from, date_to, review_status, analysis_state, limit, offset, plus metadata filters team, tier, version, label, exclude_label (each repeatable) |
| GET | /api/dashboard/active-count |
viewer | none |
fields returns full values for allowlisted paths only and is never truncated. Unknown paths return 400, so fetch /api/results/fields first when you build against it.
Review, classification, and job maintenance
| Method | Path | Min role | Body |
|---|---|---|---|
| PUT | /results/{job_id}/reviewed |
reviewer | test_name, reviewed. |
| PUT | /results/{job_id}/override-classification |
reviewer | test_name, classification |
| PUT | /results/{job_id}/override-pattern |
reviewer | test_name, pattern |
| PUT | /results/{job_id}/tags |
reviewer | JSON body {"tags": ["a", "b"]} — a list of strings. A non-list value is rejected with 400 tags must be a list of strings |
| DELETE | /results/{job_id} |
operator | none; deletes your own job, admins delete any |
| DELETE | /api/results/bulk |
operator | job_ids |
Classification values are CODE ISSUE, PRODUCT BUG, and INFRASTRUCTURE. Pattern values are NEW, REGRESSION, FLAKY, INTERMITTENT, KNOWN_BUG, and PERSISTENT. These are the exact API values; the UI renders KNOWN_BUG as Known Bug and spaces the words for readability.
Note: This table lists the role each route actually enforces. One route marked "Known gap" enforces no role beyond authentication —
vieweris therefore its effective minimum, even though the documented model reserves it forreviewer:POST /api/auth/rotate-key, tracked in #289. Treat it as a bug, not as a supported way to operate.PUT /results/{job_id}/reviewedandPUT /results/{job_id}/tagswere previously gaps as well and now requirereviewer. Review state is tracked per test; classification and pattern changes apply across the whole same-error group.
Comments and mentions
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /results/{job_id}/comments |
viewer | Full thread for one job |
| POST | /results/{job_id}/comments |
reviewer | test_name, comment; returns 201 |
| DELETE | /results/{job_id}/comments/{comment_id} |
reviewer | Comment author or admin |
| POST | /results/{job_id}/enrich-comments |
viewer | Live GitHub PR and Jira ticket status |
| POST | /api/analyze-comment-intent |
reviewer | Classifies an incoming comment as a question or an action |
| GET | /api/users/mentions |
viewer | Mentions for the current user |
| GET | /api/users/mentions/unread-count |
viewer | Navbar badge count |
| POST | /api/users/mentions/read |
viewer | Mark selected mentions read |
| POST | /api/users/mentions/read-all |
viewer | Clear the unread badge |
| GET | /api/users/mentionable |
viewer | Users who can be @-mentioned |
Trackers, issues, and exporters
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /results/{job_id}/issue-prompt |
viewer | Custom .rootcoz/ROOTCOZ_ISSUE_PROMPT.md for the repo |
| POST | /results/{job_id}/preview-github-issue |
reviewer | test_name, include_links, AI overrides; returns preview content |
| POST | /results/{job_id}/preview-jira-bug |
reviewer | Same shape, Jira flavour |
| POST | /results/{job_id}/create-github-issue |
reviewer | test_name, title, body; returns 201 |
| POST | /results/{job_id}/create-jira-bug |
reviewer | Adds jira_issue_type; returns 201 |
| PUT | /results/{job_id}/tracked-in |
reviewer | test_name, url, type |
| GET | /results/{job_id}/tracked-in |
reviewer | Current tracker links |
| DELETE | /results/{job_id}/tracked-in/{link_id} |
reviewer | Remove one link |
| GET | /api/exporters |
viewer | Available exporter plugins and enabled state |
| POST | /results/{job_id}/push/{plugin_name} |
operator | Generic push; child_job_name and child_build_number optional |
| POST | /results/{job_id}/push-reportportal |
operator | Backward-compatible Report Portal push |
Note: All four issue preview and create endpoints call
_require_reviewer, so aviewerreceives403before the allow list is consulted. They additionally require valid tracker credentials and the matchingENABLE_GITHUB_ISSUESorENABLE_JIRA_ISSUEStoggle.
History and classification lookup
| Method | Path | Min role | Query parameters |
|---|---|---|---|
| GET | /history/failures |
viewer | search, job_name, classification, from, to, limit, offset. The date bounds are named from and to on the wire, not date_from/date_to |
| GET | /history/test/{test_name} |
viewer | limit, job_name, exclude_job_id |
| GET | /history/search |
viewer | signature, exclude_job_id |
| GET | /history/stats/{job_name} |
viewer | exclude_job_id |
| GET | /history/classifications |
viewer | test_name, classification, job_name, parent_job_name, job_id |
| POST | /history/classify |
reviewer | JSON body (not query string): test_name, classification, reason, job_id, source; returns 201 |
Send source="ai" to attribute the classification to the reserved rootcoz-ai identity. The backend blocks AI callers from overriding classifications a human has already set.
Chat
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /api/chat/{job_id} |
viewer | limit (1–500), offset |
| POST | /api/chat/{job_id} |
reviewer | message plus optional AI overrides; returns 202, answer arrives over SSE |
| POST | /api/chat/{job_id}/init |
reviewer | Explicit ai_provider, ai_model, force_server_credentials |
| POST | /api/chat/{job_id}/abort |
reviewer | Cancel the in-flight answer |
| POST | /api/chat/{job_id}/close |
reviewer | Close the AI session |
| DELETE | /api/chat/{job_id} |
reviewer | Clear chat history |
| GET | /api/chat/{job_id}/stream |
viewer | SSE stream of new messages |
Server-Sent Events streams
| Path | Min role | Pushes |
|---|---|---|
/api/stream |
viewer | Multiplexed stream; subscribe with ?topics=navbar,dashboard,results:JOB_ID,comments:JOB_ID,chat:JOB_ID |
/api/navbar/stream |
viewer | Active analysis count and unread mentions |
/api/dashboard/stream |
viewer | Job list changes |
/api/results/{job_id}/stream |
viewer | Per-job status changes |
/api/results/{job_id}/comments/stream |
viewer | Per-job comment changes |
/api/chat/{job_id}/stream |
viewer | Per-job chat messages |
/api/admin/token-usage/stream |
admin | Token usage changes. Enforced by middleware before the handler runs |
/api/admin/logs/stream |
admin | Live server log tail; lines and level query parameters |
/api/admin/settings/stream |
admin | Server setting changes |
Use /api/stream when you want one connection instead of six. It accepts up to 50 comma-separated topics; token-usage and settings are delivered only to admins.
Reports and analytics
| Method | Path | Min role | Query parameters |
|---|---|---|---|
| GET | /api/reports/totals |
admin or can_view_reports |
team, tier, version, from, to, status, tags, exclude_tags, review_status, limit, offset |
| GET | /api/reports/classification-overrides |
admin or can_view_reports |
same filter set |
| GET | /api/reports/issues-created |
admin or can_view_reports |
same filter set |
Date bounds use the from and to query names. Without the flag you get 403 Reports access required.
Metadata assignment
| Method | Path | Min role |
|---|---|---|
| GET | /api/jobs/metadata |
viewer |
| GET | /api/jobs/{job_name}/metadata |
viewer |
| PUT | /api/jobs/{job_name}/metadata |
admin |
| DELETE | /api/jobs/{job_name}/metadata |
admin |
| PUT | /api/jobs/metadata/bulk |
admin |
| GET | /api/jobs/metadata/rules |
viewer |
| POST | /api/jobs/metadata/rules/preview |
viewer |
Notifications and AI discovery
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /api/notifications/vapid-public-key |
viewer | Web Push public key |
| POST | /api/notifications/subscribe |
viewer | endpoint, p256dh_key, auth_key |
| POST | /api/notifications/unsubscribe |
viewer | Removes a subscription |
| GET | /api/ai-models |
viewer | provider, force_server_credentials; lists models for claude, gemini, cursor |
| GET | /api/capabilities |
viewer | Feature toggles and whether server credentials exist |
| GET | /api/default-server-settings |
viewer | Non-sensitive settings and defaults only |
| POST | /api/feedback/preview |
viewer | AI-drafted feedback issue |
| POST | /api/feedback/create |
viewer | Creates the GitHub issue; returns 201 |
Admin
| Method | Path | Notes |
|---|---|---|
| GET | /api/admin/users |
List users |
| GET | /api/admin/users/pending |
Accounts awaiting approval |
| POST | /api/admin/users/create |
username, role, can_view_reports, can_use_server_providers |
| DELETE | /api/admin/users/{username} |
Delete a user |
| PUT | /api/admin/users/{username}/role |
Change role |
| POST | /api/admin/users/{username}/approve |
Approve a pending registration |
| POST | /api/admin/users/{username}/reject |
Reject a pending registration |
| POST | /api/admin/users/{username}/rotate-key |
Re-issue a user's API key |
| PUT | /api/admin/users/{username}/can-view-reports |
Grant or revoke reports access |
| PUT | /api/admin/users/{username}/can-use-server-providers |
Grant or revoke server AI credentials |
| GET | /api/admin/free-models |
List admin-declared free provider+model pairs (with version fingerprint) |
| PUT | /api/admin/free-models |
Replace the free pair list (validated against the catalog); optional expected_version from a prior GET rejects concurrent edits with 409 |
| GET | /api/admin/settings |
All settings with value, source, and metadata; reveal_key unmasks one key or __all__ |
| PUT | /api/admin/settings |
{"settings": {"KEY": "value"}} |
| GET | /api/admin/settings/history |
key, limit change history |
| DELETE | /api/admin/settings/{key} |
Reset one setting to its default |
| GET | /api/admin/token-usage |
start_date, end_date, ai_provider, ai_model, call_type, group_by |
| GET | /api/admin/token-usage/summary |
Dashboard totals |
| GET | /api/admin/token-usage/{job_id} |
Per-job usage |
| POST | /api/admin/ai-models/refresh |
Re-discover models from the sidecar |
| GET | /api/admin/component-versions |
Backend, sidecar, and chart versions |
| GET | /api/admin/db/schema |
Read-only schema dump |
| POST | /api/admin/db/query |
Read-only SQL for the admin chat |
| GET | /api/admin/chat |
Admin chat history |
| POST | /api/admin/chat |
Send an admin chat message; returns 202 |
| POST | /api/admin/chat/init, /close, /abort |
Admin chat session control |
| DELETE | /api/admin/chat |
Clear admin chat history |
| GET | /api/admin/chat/stream |
Admin chat SSE |
| POST | /api/admin-chat/artifacts |
Save a generated HTML report artifact |
| GET | /api/admin-chat/artifacts/{artifact_id} |
Download that artifact |
See Configuration Reference for the full environment variable surface behind these admin settings.
Advanced Usage
Prefer the CLI when you can. Most endpoints above have a rootcoz command counterpart. SSE streams have none, because the CLI is a one-shot tool, and the user-token endpoints (GET/PUT /api/user/tokens) have client methods but no registered CLI command either. See CLI Command Reference and Automating Common Tasks with the CLI.
A three-call integration that skips the UI entirely:
KEY="$ROOTCOZ_API_KEY"
BASE=http://localhost:8000
JOB=$(curl -s -X POST "$BASE/analyze" \
-H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"type":"file","raw_xml":"<testsuite>...</testsuite>","labels":["nightly"]}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["job_id"])')
curl -s -H "Authorization: Bearer $KEY" "$BASE/results/$JOB" | jq '.status'
curl -s -H "Authorization: Bearer $KEY" \
"$BASE/results/$JOB?fields=status,result.summary"
For long-lived integrations, open /api/stream?topics=navbar,results:$JOB once instead of polling the result endpoint.
Troubleshooting
-
403 Authentication requiredor a401on a supposedly public path.
The path is not in the public allow list. UsePOST /api/auth/loginfor a session cookie or sendAuthorization: Bearer <API_KEY>. -
403 Admin access requiredon/api/admin/*.
Your account is notadmin. The bootstrapadminsuperuser is created fromADMIN_KEY; see Managing Users and Server Settings. -
403 Reports access requiredfrom/api/reports/*.
You need thecan_view_reportsflag, which is independent of role. Admins always have it. -
400fromGET /results/{job_id}?fields=....
One path is not on the allowlist. FetchGET /api/results/fieldsand use an exact path. -
404on/api/results/{job_id}/testsfor tests you expect.
A404here means the job ID does not exist — the endpoint checks that before applying any filter, so no amount ofchild_job_nameorchild_build_numberwill help. An existing job that simply has no matching entries returns200with an emptyentrieslist; to narrow or widen that, confirm the status filter matchespassed,skipped, orfailed, and addchild_job_nameandchild_build_numberwhen the tests live under a child job. -
403onPOST /analyzeright after registering.
Your default role isvieweror the account is still pending approval. CheckGET /api/auth/me, then ask an admin to raise your role or approve the account.