5 min read
API Reference
StateAnchor exposes REST endpoints for the GitHub Action, programmatic integrations, and public project status. All endpoints return JSON.
Authentication
StateAnchor uses two authentication methods depending on the caller:
| Method | Used by | How it works |
|---|---|---|
| Clerk session | Browser / dashboard | Automatic via cookie. Used by all /api/projects/* and dashboard routes. |
| GitHub OIDC action token | GitHub Action (machine) | Short-lived token minted via /api/action/oidc/exchange. Passed as Bearer token in the Authorization header. Used by /api/action/* endpoints. |
The action token flow: GitHub provides an OIDC JWT → the action exchanges it with StateAnchor → StateAnchor returns a scoped action token bound to the repo, ref, and event type. This token is valid for 5 minutes.
Base URL
https://stateanchor.devAll endpoint paths below are relative to this base URL.
POST /api/action/validate
Validates a stateanchor.yaml spec file. Returns validation result and parsed config summary.
Authentication
Required. Action token via Authorization: Bearer <action_token>.
Request body
{
"config_content": "<raw YAML string>",
"repo": "owner/repo",
"ref": "refs/heads/main",
"commit_sha": "abc123..."
}Success response (200)
{
"valid": true,
"config_summary": {
"service": "payments-api",
"version": "1.0.0",
"endpoint_count": 3,
"outputs": ["typescript", "python", "mcp"]
}
}Error responses
| Status | Error code | Description |
|---|---|---|
| 401 | invalid_action_token | Token missing, expired, or invalid. |
| 403 | repo_not_linked | Repository is not connected in StateAnchor. |
| 403 | ref_not_allowed | Branch/ref is not in the allowed mappings. |
| 422 | invalid_config | YAML parse error or schema validation failure. Includes errors array. |
| 429 | rate_limited | Too many requests. Retry after the Retry-After header value. |
POST /api/action/gate-check
Diffs the incoming spec against the previous IR, scores breaking changes, evaluates the gate policy, creates a sync run, and returns the gate decision. This is the primary endpoint the GitHub Action calls after validation passes.
Authentication
Required. Action token via Authorization: Bearer <action_token>.
Request body
{
"config_content": "<raw YAML string>",
"repo": "owner/repo",
"ref": "refs/heads/main",
"commit_sha": "abc123...",
"mode": "sync"
}Success response (200)
{
"gate_action": "proceed",
"gate_lane": "INFO",
"gate_reason": "PASS -- additive change only",
"effective_breaking_score": 0,
"sync_run_id": "fb87c28a-4509-4463-9fc0-ac35e2438c3b",
"breaking_operations": [],
"diff_summary": {
"errors": 0,
"warnings": 0,
"info": 1
}
}Blocked response (200 with gate_action: block)
{
"gate_action": "block",
"gate_lane": "ERR",
"gate_reason": "BLOCK -- breaking removal detected",
"effective_breaking_score": 40,
"sync_run_id": "a1b2c3d4-...",
"breaking_operations": [
{
"kind": "endpoint_removed",
"endpoint": "GET /users",
"breaking_score": 40,
"severity": "error"
}
]
}Error responses
| Status | Error code | Description |
|---|---|---|
| 401 | invalid_action_token | Token missing, expired, or invalid. |
| 403 | repo_not_linked | Repository is not connected in StateAnchor. |
| 403 | ref_not_allowed | Branch/ref is not in the allowed mappings. |
| 422 | invalid_config | Spec failed validation. |
| 429 | rate_limited | Too many requests. |
POST /api/projects/:id/sync
Triggers a manual sync for a project. Creates a new sync run, validates the current spec version, diffs against the previous IR, evaluates the gate, and generates output artifacts.
Authentication
Required. Clerk session cookie (browser / dashboard).
Request body
{
"trigger_type": "manual" // optional: "manual" (default), "webhook", or "upgrade"
}Success response (200)
{
"ok": true,
"sync_run_id": "fb87c28a-4509-4463-9fc0-ac35e2438c3b",
"status": "completed"
}Error responses
| Status | Description |
|---|---|
| 401 | Not authenticated -- no valid Clerk session. |
| 404 | Project not found or does not belong to the authenticated user. |
| 500 | Sync failed. Response includes sync_run_id, error, and status: "failed". |
GET /api/projects/:id/sync-runs
Returns the 20 most recent sync runs for a project, ordered by newest first.
Authentication
Required. Clerk session cookie (browser / dashboard).
Success response (200)
[
{
"id": "fb87c28a-4509-4463-9fc0-ac35e2438c3b",
"project_id": "a1b2c3d4-...",
"status": "completed",
"trigger_type": "manual",
"version_number": 3,
"created_at": "2026-03-27T12:00:00Z",
"started_at": "2026-03-27T12:00:01Z",
"completed_at": "2026-03-27T12:00:04Z",
"error_message": null,
"gate_action": "proceed",
"breaking_score": 0,
"effective_breaking_score": 0,
"total_duration_ms": 3200
}
]Error responses
| Status | Description |
|---|---|
| 401 | Not authenticated -- no valid Clerk session. |
| 404 | Project not found or does not belong to the authenticated user. |
POST /api/webhooks/github
Receives GitHub push events. When a push modifies or adds stateanchor.yaml, the webhook fetches the updated spec, validates it, creates a new project version and sync run, and enqueues the sync for async processing.
Authentication
GitHub webhook signature. The x-hub-signature-256 header is verified against the configured GITHUB_WEBHOOK_SECRET using HMAC-SHA256 with timing-safe comparison.
Request body
Standard GitHub push event payload. No custom fields required.
Behavior
- Only processes
pushevents -- all other event types are ignored. - Returns
200 OKimmediately ifstateanchor.yamlwas not modified. - Looks up the repo by
github_repo_idand validates the spec before creating a sync run. - Always returns
200for valid signatures, even if downstream processing fails.
Error responses
| Status | Description |
|---|---|
| 401 | Missing or invalid x-hub-signature-256 header. |
| 500 | Internal error (rare -- most failures return 200 to avoid GitHub retries). |
POST /api/waitlist
Public endpoint. Adds an email to the StateAnchor waitlist. No authentication required.
Request body
{
"email": "user@example.com"
}Success response (200)
{
"ok": true
}Error responses
| Status | Error code | Description |
|---|---|---|
| 400 | missing_email | Email field is missing or empty. |
| 409 | already_registered | Email is already on the waitlist. |
GET /api/projects/list
Returns all projects belonging to the authenticated user, with sync stats and configured outputs.
Authentication
Required. Clerk session cookie.
Success response (200)
{
"github_connected": true,
"onboarding_completed": true,
"projects": [
{
"project_id": "b29820bb-...",
"name": "onboarding-test",
"repo_full_name": "owner/repo",
"status": "active",
"drift_detected": false,
"last_synced_at": "2026-03-29T20:48:00Z",
"version": "1.0.0",
"outputs": ["typescript", "python", "mcp"],
"artifact_count": 17,
"total_runs": 12
}
]
}GET /api/projects/:id/artifacts/latest
Returns the latest generated artifacts for a project from the most recent completed sync run.
Authentication
Required. Clerk session cookie.
Success response (200)
{
"latest_sync_run_id": "fb87c28a-...",
"artifacts": {
"wrappers": [
{
"id": "...",
"language": "typescript",
"artifact_type": "wrapper",
"content": "// Generated TypeScript SDK...",
"sha256": "a3f9c2d1..."
}
],
"mcp": { "id": "...", "artifact_type": "mcp", "content": "..." },
"docs": null
}
}GET /api/projects/:id/drift-check
Checks whether the stateanchor.yaml file on GitHub has changed since the last completed sync. Compares Git blob SHAs.
Authentication
Required. Clerk session cookie.
Success response (200)
{
"drifted": false,
"last_synced_sha": "a3f9c2d1e8f4...",
"current_sha": "a3f9c2d1e8f4...",
"last_synced_at": "2026-03-29T20:48:00Z"
}POST /api/action/oidc/exchange
Exchanges a GitHub OIDC JWT for a scoped StateAnchor action token. Called by the GitHub Action at the start of every workflow run. The returned token is valid for 5 minutes.
Request body
{ "token": "<GitHub OIDC JWT>" }Success response (200)
{
"action_token": "<scoped token>",
"expires_in": 300,
"repo": "owner/repo",
"ref": "refs/heads/main"
}Error responses
| Status | Description |
|---|---|
| 401 | Invalid or expired OIDC token. |
| 403 | Repository not registered in StateAnchor. |
POST /api/v1/keys
Creates a new API key for programmatic access.
Authentication
Required. Clerk session cookie.
Request body
{
"name": "CI integration key",
"scopes": ["read", "sync"]
}Valid scopes: read, write, sync.
Success response (201)
{
"id": "key_abc123...",
"key": "sa_live_...",
"name": "CI integration key",
"scopes": ["read", "sync"],
"created_at": "2026-03-29T20:48:00Z"
}Important: The key field is only returned at creation time. Store it securely.
GET /api/v1/keys
Lists all API keys for the authenticated user. Key values are not returned.
Authentication
Required. Clerk session cookie.
Success response (200)
{
"keys": [
{
"id": "key_abc123...",
"name": "CI integration key",
"prefix": "sa_live_a3f9",
"scopes": ["read", "sync"],
"last_used_at": "2026-03-29T18:00:00Z",
"created_at": "2026-03-28T12:00:00Z"
}
]
}GET /api/public/:projectId/status
Public. Returns project status, drift state, and output types. No auth required.
Success response (200)
{
"project": "payments-api",
"status": "active",
"drifted": false,
"last_synced_at": "2026-03-29T20:48:00Z",
"outputs": ["typescript", "python", "mcp"]
}GET /api/public/:projectId/sdk/:language
Public. Returns the latest generated SDK as raw text.
Path parameters
| Parameter | Description |
|---|---|
language | typescript, python, or go |
GET /api/public/:projectId/mcp
Public. Returns the latest generated MCP server as raw text.
GET /api/health
Public health check.
Success response (200)
{ "status": "ok", "timestamp": "2026-03-29T20:48:00Z", "version": "1.0.0" }DELETE /api/user/delete
Permanently deletes the authenticated user's account and all data. Removes child records first, then user row, then Clerk user. Irreversible.
Authentication
Required. Clerk session cookie.
Success response (200)
{ "ok": true }Rate limits
| Endpoint | Limit | Scope |
|---|---|---|
/api/action/validate | 10 requests / minute | Per repository |
/api/action/gate-check | 60 requests / minute | Per repository |
/api/projects/:id/sync | None | Clerk session |
/api/projects/:id/sync-runs | None | Clerk session |
/api/webhooks/github | None | GitHub signature |
/api/waitlist | 5 requests / minute | Per IP |
Rate-limited responses return 429 with a Retry-After header indicating seconds to wait before retrying.
Error format
All error responses use a consistent JSON shape:
{
"error": "error_code_string"
}The error field is a machine-readable snake_case code. Some endpoints include additional fields like errors (array of validation issues) ordetail (human-readable explanation).
// Example: validation failure with details
{
"error": "invalid_config",
"errors": [
"endpoints[0]: missing required field 'method'",
"outputs: at least one output must be enabled"
]
}