Base URL: http://127.0.0.1:8372/api/v1 (port configurable via OCR_CC_PORT).
All requests/responses are JSON unless noted. Interactive docs: /api/docs
(Swagger UI) and /openapi.json. (/docs belongs to the in-app documentation
page.)
Conventions
- State-changing requests (POST/PATCH/DELETE) require the CSRF double-submit:
read the
ocrcc_csrfcookie set by any safe response and echo it in theX-OCR-CSRFheader. Missing/mismatched →403 csrf_failed. - List endpoints return
{"items": [...], "total": n, "limit": n, "offset": n}and acceptlimit/offset. - Errors share one envelope:
{
"error": {
"code": "validation_failed",
"message": "target_ref 'does-not-exist' is not a valid ref.",
"detail": "...",
"next_action": "Pick an existing branch or tag."
}
}Common codes: not_found (404), validation_failed (422), conflict
(409), csrf_failed (403), unsupported_feature (422).
GET /folders list folders
POST /folders {"display_name"?, "absolute_path", "scan_depth"?, "auto_discover"?} → 201 Folder
GET /folders/{id} folder detail
PATCH /folders/{id} partial update
DELETE /folders/{id} remove (projects stay)
POST /folders/{id}/scan discover git repos → {"repos": [{"path","name","already_registered",...}]}
POST /folders/{id}/register register discovered repos as projects
GET /projects list projects
POST /projects {"absolute_path", "display_name"?, "folder_id"?} → 201 Project
GET /projects/{id} detail (includes branch cache freshness)
PATCH /projects/{id} partial update
DELETE /projects/{id}
POST /projects/{id}/refresh-branches re-read local+remote refs (no restart needed)
POST /projects/{id}/fetch git fetch --prune, then refresh
GET /projects/{id}/branches [{"name","kind":"local|remote","current","sha","last_commit_at"}]
GET /projects/{id}/pull-requests open PRs → {"prs":[{number,title,head_ref,head_sha,
base_ref,base_sha,author,updated_at,source}],
"source":"api|git|none","warning"?}
GitHub remotes use the REST API (optional
OCR_CC_GITHUB_TOKEN env); everything else falls
back to `git ls-remote refs/pull/*/head`
(number + head_sha only — base chosen manually)
GET /projects/{id}/jobs jobs for this project (paged)
GET /providers list (never includes credential material)
POST /providers → 201; body below
GET /providers/{id}
PATCH /providers/{id} {"credential": null} clears the stored credential
DELETE /providers/{id}
POST /providers/{id}/test direct minimal ping ("Reply with exactly: hi") against the
endpoint — requires ?model_id=… → {"ok","reply","elapsed_ms",
"http_status","message","detail","next_action"}
POST /providers/{id}/discover-models query the endpoint's model list → discovered models upserted
GET /providers/{id}/models list models for the provider
POST /providers/{id}/models manual model {"model_id"} → 201
DELETE /providers/{id}/models/{model_pk}
// POST /providers
{
"name": "Anthropic",
"protocol": "anthropic", // anthropic | openai | openai-responses
"base_url": "https://api.anthropic.com",
"credential": "sk-…" | "env:VAR_NAME", // stored in OS keyring / resolved from env
"auth_header": null, // custom auth header name
"extra_headers": {}, "extra_body": {},
"http_timeout_seconds": 60
}Responses carry has_credential: true|false — never the credential or its
reference.
GET /review-profiles
POST /review-profiles → 201; body below
GET /review-profiles/{id}
PATCH /review-profiles/{id}
DELETE /review-profiles/{id}
POST /review-profiles/{id}/duplicate → 201 "<name> copy"
A profile bundles: provider_profile_id, model_id, language,
concurrency, per_file_timeout_minutes, llm_http_timeout_seconds,
max_tools, max_git_processes, plan_mode (auto|always|never),
plan_threshold_lines, max_tokens, exclude_patterns, rule_file_path,
tools_file_path, background_template, and expert additional_arguments.
Planning controls are validated against detected OCR capabilities at job
creation; jobs on a stock binary fall back to auto and are marked
accordingly.
GET /jobs filters: status, project_id, source, provider_id
POST /jobs → 201 Job (status "queued"); body below
GET /jobs/{id} detail incl. snapshot, generated command, findings_count
?wait_for_terminal=true&timeout_seconds=300 (1–600) blocks
server-side until a terminal status or the timeout, then
returns the latest detail (status may be non-terminal on timeout)
PATCH /jobs/{id} {"priority"} (queued jobs only)
DELETE /jobs/{id} 204 (running jobs must be cancelled first)
POST /jobs/preview same body as create minus queue options → preview JSON
POST /jobs/{id}/cancel running → cancelling → cancelled; queued → cancelled
POST /jobs/{id}/retry → 201 new job linked via retry_of_job_id
POST /jobs/{id}/resume → 201 new job resuming the OCR session
POST /jobs/{id}/duplicate → 201 copy with same inputs
POST /jobs/{id}/move {"action":"top|up|down"} manual queue ordering
POST /jobs/{id}/pause pause a queued job
POST /jobs/{id}/resume-paused unpause
GET /jobs/{id}/events SSE stream (see below)
GET /jobs/{id}/events/history persisted events (?limit&offset)
GET /jobs/{id}/findings paged; filters: user_state, path, include_reasoning
GET /jobs/{id}/findings/{finding_id} single finding; ?include_reasoning=true for thinking
PATCH /jobs/{id}/findings/{finding_id} {"user_state","user_note"} (response never includes thinking)
GET /jobs/{id}/warnings warnings array from the OCR result
GET /jobs/{id}/logs ?stream=stdout|stderr&tail_bytes=64000 (redacted)
GET /jobs/{id}/session raw session records; filters below
GET /jobs/{id}/export ?format=md|json|csv|jsonl|txt|agent-prompt|github-summary
&include_reasoning=false → file download
// POST /jobs
{
"project_id": "…",
"mode": "range", // range | commit | workspace | pr | scan
"base_ref": "main", "target_ref": "feature/x", // range
"commit_ref": "HEAD", // commit
"pr_number": 7, // pr (base_ref optional —
// required only when the PR base cannot be
// resolved automatically; base/target SHAs
// are captured immutably at queue time and
// the job runs as a range review in a
// detached worktree)
"profile_id": "…", // optional; defaults used otherwise
"background": "…", "background_file": "…",
"exclude_patterns": ["dist/**"],
"priority": 50,
"webhook_endpoint_id": "…" // or inline webhook_url/webhook_secret
}SSE GET /jobs/{id}/events — text/event-stream. Replays persisted
events with id > Last-Event-ID (header or ?lastEventId=), then streams
live events with 15 s keepalives. The stream closes after a terminal event
(job.status with to ∈ terminal states). Event types: job.queued,
job.status, job.phase, job.file_started, job.file_completed,
job.warning, job.usage, job.summary, job.log. job.usage contains
cumulative input_tokens and output_tokens for the active review.
Reasoning (opt-in) — thinking is null everywhere unless
include_reasoning=true is passed on the findings endpoints or the export
(SPEC §38.15).
Session inspector filters — GET /jobs/{id}/session accepts q
(case-insensitive substring over the raw record), task_type (exact match:
plan_task, main_task, review_filter_task, memory_compression_task,
re_location_task), and file (path substring). total reflects the
filtered count; limit/offset paginate after filtering.
GET /queue {"paused": bool, "jobs": [Job…]}
POST /queue/pause stop dispatching (running jobs finish)
POST /queue/resume
POST /queue/reorder {"job_ids": […]} transactional reorder
POST /queue/clear-completed remove terminal jobs → {"removed": n}
GET /webhooks list (has_secret only)
POST /webhooks {"name","url","secret"?,"allowed_events"?,"enabled"?} → 201
PATCH /webhooks/{id} incl. {"rotate_secret": true} → returns the new secret once
DELETE /webhooks/{id}
POST /webhooks/{id}/test send a test delivery
GET /webhooks/{id}/deliveries delivery log (status, http_status, attempt, next_attempt_at)
POST /webhook-deliveries/{id}/replay requeue a delivery
Payload/signing reference: WEBHOOKS.md.
GET /health {"status":"ok","version","ocr_status"}
GET /system/info full diagnostics snapshot (SPEC §30)
GET /system/ocr OCR detection + capabilities; "ocr_not_found" when absent
POST /system/ocr/test force re-probe
GET /system/mcp MCP server status: transport, URL, tool/resource/prompt counts
GET /system/diagnostics/bundle sanitized zip download (no credentials, no source
content; log excerpts capped at 16 KB and redacted)
GET /system/python interpreter info
GET /settings editable settings map
PATCH /settings {"changes": {"queue.global_concurrency": 2, …}}
Editable setting keys: queue.global_concurrency,
queue.per_project_concurrency, queue.per_provider_concurrency,
queue.paused, retention.artifact_days, retention.keep_worktrees,
webhooks.require_https, webhooks.allow_private_networks,
ocr.executable, git.executable.
POST /mcp — MCP Streamable HTTP endpoint (tools, resources, prompts).
See MCP.md.