REST API
The live OpenAPI docs are available on your running Synth deployment:
https://synth.example.com/api/docs
This page shows the calls most users need.
Set your deployment URL once before running the examples:
export SYNTH_URL=https://synth.example.com
Auth
On a fresh install, create the first admin account and save its session cookie:
curl -c /tmp/synth.cookie -X POST $SYNTH_URL/api/auth/setup \
-F username=admin \
-F password='Admin12345!' \
-F display_name=Admin
Create an API key with that saved admin session cookie:
SYNTH_API_KEY=$(curl -s -b /tmp/synth.cookie \
-H "Content-Type: application/json" \
-d '{"name":"docs-key","scopes":["knowledge:read","ingest:write"]}' \
$SYNTH_URL/api/auth/api-keys | jq -r .key)
Use it:
Authorization: Bearer synth_sk_REPLACE_ME
The examples below use a scoped API key for knowledge, ingest, and watcher calls. Project creation uses the admin session cookie from setup.
Projects
Create a project:
curl -b /tmp/synth.cookie \
-H "Content-Type: application/json" \
-d '{"name":"docs","backend_type":"local"}' \
$SYNTH_URL/api/projects/
Set a reusable project path from the active project:
PROJECT_PATH=$(curl -s -b /tmp/synth.cookie \
$SYNTH_URL/api/projects/active | jq -r .path)
Set active project:
curl -b /tmp/synth.cookie -X PUT \
-H "Content-Type: application/json" \
-d "{\"project_path\":\"$PROJECT_PATH\"}" \
$SYNTH_URL/api/projects/active
Create a Git-backed project:
curl -X POST $SYNTH_URL/api/projects/ \
-b /tmp/synth.cookie \
-H "Content-Type: application/json" \
-d '{
"name": "backend-api",
"backend_type": "git",
"backend_config": {
"remote_url": "https://github.com/your-org/synth-knowledge.git",
"branch": "main",
"project_subdir": "projects/backend-api",
"sync_on_create": true,
"auto_pull": true,
"auto_push": true,
"credential_id": "github-read-token"
}
}'
Ingest
URL:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://modelcontextprotocol.io/introduction\",\"project_path\":\"$PROJECT_PATH\"}" \
$SYNTH_URL/ingest/api/url
File:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-F "project_path=$PROJECT_PATH" \
-F "file=@README.md" \
$SYNTH_URL/ingest/api/file
Git:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://github.com/modelcontextprotocol/modelcontextprotocol\",\"project_path\":\"$PROJECT_PATH\",\"branch\":\"main\"}" \
$SYNTH_URL/ingest/api/git
GitLab uses the same endpoint:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://gitlab.com/your-group/your-project\",\"project_path\":\"$PROJECT_PATH\",\"branch\":\"main\",\"credential_id\":\"gitlab-read-token\"}" \
$SYNTH_URL/ingest/api/git
Poll:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
"$SYNTH_URL/ingest/api/status/$JOB_ID?project_path=$PROJECT_PATH"
OKF-Native Knowledge Bundles
Synth is OKF-native for project knowledge. The live knowledge/ tree is stored
as Google Open Knowledge Format v0.1-compatible Markdown; the OKF API validates,
downloads, and imports that project-scoped knowledge.
Status:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
"$SYNTH_URL/api/projects/okf/status?project_path=$PROJECT_PATH"
Validate OKF readiness:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
"$SYNTH_URL/api/projects/okf/status?validate=true&project_path=$PROJECT_PATH"
Validate a ZIP:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-F "project_path=$PROJECT_PATH" \
-F "file=@bundle.zip" \
$SYNTH_URL/api/projects/okf/validate
Start export:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"project_path\":\"$PROJECT_PATH\"}" \
$SYNTH_URL/api/projects/okf/export
Preview import before mutation:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-F "project_path=$PROJECT_PATH" \
-F "file=@bundle.zip" \
$SYNTH_URL/api/projects/okf/import/validate
Apply import:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-F "project_path=$PROJECT_PATH" \
-F "file=@bundle.zip" \
-F "overwrite=false" \
$SYNTH_URL/api/projects/okf/import/apply
Export and validation require project viewer access with read scope. Import
requires project editor access with full_access, ingest:write, or
knowledge:write. The API intentionally does not expose remote OKF publish or
OKF-specific MCP controls.
Canonical Project Search And Exact Read
Search one stable Project ID:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id":"'$PROJECT_ID'","query":"MCP protocol","limit":5}' \
"$SYNTH_URL/api/v1/projects/$PROJECT_ID/search"
Read an exact returned page revision:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id":"'$PROJECT_ID'","page":"'$PAGE_ID'","expected_snapshot_id":"'$SNAPSHOT_ID'","expected_revision":"'$REVISION'"}' \
"$SYNTH_URL/api/v1/projects/$PROJECT_ID/pages/read"
Graph
Graph data:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
"$SYNTH_URL/api/graph/data?project_path=$PROJECT_PATH"
Graph stats:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
"$SYNTH_URL/api/graph/stats?project_path=$PROJECT_PATH"
Watchers
Watcher mutations require an API key with watchers:manage (or full_access)
and editor access to the target project. Watcher reads require
knowledge:read, repo_context:read, or full_access and viewer access. Use
the web Watchers screen first if you are still tuning file rules.
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"watcher_type\":\"git\",\"watcher_id\":\"docs\",\"config\":{\"git_url\":\"https://github.com/modelcontextprotocol/modelcontextprotocol\",\"branch\":\"main\",\"poll_interval\":60,\"files\":{\"rules\":[{\"name\":\"docs\",\"include\":[\"**/*.{md,mdx,rst,txt,markdown,yml,yaml,tf,tfvars}\",\"**/*.tf.json\"],\"exclude\":[\"**/reports/**\"]}]},\"project_path\":\"$PROJECT_PATH\"}}" \
$SYNTH_URL/api/watchers
Create a GitLab group watcher, including nested subgroups:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"watcher_type\":\"git\",\"watcher_id\":\"platform-group\",\"credential_id\":\"gitlab-read-token\",\"config\":{\"provider\":\"gitlab\",\"org_url\":\"https://gitlab.com/acme/platform\",\"include_subgroups\":true,\"include_archived\":false,\"include_shared\":false,\"branch\":\"main\",\"poll_interval\":60,\"auto_start\":true,\"project_path\":\"$PROJECT_PATH\"}}" \
$SYNTH_URL/api/watchers
auto_start belongs inside config. New watcher requests default it to true
when omitted, but setting it explicitly makes automation intent auditable.
Preview Git watcher file rules without creating a watcher:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"watcher_type":"git","config":{"repo_path":"/var/lib/synth/imports/docs-repo","files":{"rules":[{"name":"docs","include":["docs/**/*.{md,mdx,txt,yml,yaml,tf,tfvars}","docs/**/*.tf.json"],"exclude":["docs/**/reports/**"]}]}}}' \
$SYNTH_URL/api/watchers/preview-files
Preview responses include included, excluded, and ignored samples with the exact include or exclude pattern that produced the decision.
Preview Jira watcher scope without creating a watcher:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"site_url":"https://example.atlassian.net","credential_id":"jira-prod","jql":"updated >= -30d ORDER BY updated DESC"}' \
$SYNTH_URL/api/watchers/preview-jira
Create a Jira watcher:
curl -H "Authorization: Bearer $SYNTH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"watcher_type":"jira","watcher_id":"roadmap","credential_id":"jira-prod","config":{"site_url":"https://example.atlassian.net","jql":"updated >= -30d ORDER BY updated DESC","poll_interval":300,"comments":{"mode":"latest","limit":10},"changelog":{"enabled":true},"privacy":{"user_display":"display_name","include_emails":false}}}' \
$SYNTH_URL/api/watchers
Common watcher actions:
curl -H "Authorization: Bearer $SYNTH_API_KEY" $SYNTH_URL/api/watchers
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST $SYNTH_URL/api/watchers/docs/check-now
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST $SYNTH_URL/api/watchers/docs/stop
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST $SYNTH_URL/api/watchers/docs/start
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST $SYNTH_URL/api/watchers/docs/logs/clear
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST $SYNTH_URL/api/watchers/platform-group/reconcile
curl -H "Authorization: Bearer $SYNTH_API_KEY" -X POST \
-H "Content-Type: application/json" \
-d "{\"approve\":true,\"snapshot_digest\":\"$BLOCKED_SNAPSHOT_DIGEST\"}" \
$SYNTH_URL/api/watchers/platform-group/catalog-removals/approve
Set BLOCKED_SNAPSHOT_DIGEST to the full blocked_snapshot_digest returned by
the latest GET /api/watchers response. The approval is rejected if the
catalog changes before execution and is intentionally one-use. Failed or
incomplete collection enumeration retains the last trusted repository catalog
and cannot remove repositories.
For collection watchers, GET /api/watchers returns separate catalog and
repository_freshness objects. The catalog includes status, generation,
repository_count, last_complete_at, last_attempt_at, age_seconds,
authenticated, missing_candidates, pending_additions,
blocked_snapshot_digest, and active_generation. Repository freshness
includes bounded counts for fresh, checking, retrying, blocked,
empty, and unknown, plus at most 100 safe result rows.
Collection safety settings are optional and server-bounded:
{
"catalog_missing_confirmations": 2,
"catalog_confirmation_window_seconds": 300,
"catalog_max_removals": 20,
"catalog_max_removal_fraction": 0.25,
"catalog_repo_concurrency": 4
}
The server accepts 1–10 confirmations, a 0–604800 second confirmation window, 0–10000 absolute removals, a 0–1 removal fraction, and 1–16 concurrent repository bootstraps. Values outside these limits are rejected rather than silently clamped.
Error Shape
FastAPI returns normal JSON errors:
{
"detail": "Not authenticated"
}
For MCP, use the JSON-RPC endpoint instead: MCP for AI Agents.