You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A terse catalog of every REST endpoint Breadbox exposes. The canonical machine-readable spec is openapi.yaml; this file is the human-readable index. Keep both in sync — see .claude/rules/api-endpoints.md for the upkeep rule.
All endpoints live under /api/v1/ and require X-API-Key unless noted. Scope column: R = readable with any key, W = requires full_access scope. Auth and CSRF details live in .claude/rules/api.md.
Health / meta
Method
Path
Scope
Description
GET
/health
none
Basic liveness, no DB ping
GET
/health/live
none
Alias of /health
GET
/health/ready
none
DB + scheduler readiness
GET
/api/v1/version
none
Build version + upgrade check
GET
/api/v1/headless/bootstrap
R
Setup readiness report (consumed by breadbox doctor)
Auth
Unauthenticated device-code dance the CLI uses to mint API keys on a remote host without a paste-mode token. The device_code returned by the initiate call is the credential the polling endpoint accepts; the user_code is the short human-facing string the operator types on the browser approval page (GET /auth/device, session-gated, not on the public REST surface).
Method
Path
Scope
Description
POST
/auth/device-code
none
Mint a pending device-code pair; returns device_code, user_code, verification_url, expires_in, interval
POST
/auth/device-code/poll
none
Poll status; 200 {status: "authorization_pending"} or 200 {status: "approved", token: "bb_..."}, with 400 EXPIRED / 400 DENIED / 404 INVALID_DEVICE_CODE envelopes for terminal states
Accounts
Method
Path
Scope
Description
GET
/accounts
R
List all household accounts
GET
/accounts/{id}
R
Single account summary
GET
/accounts/{id}/detail
R
Detail incl. last 25 transactions and per-currency balances
Apply one rule retroactively (set_category last-writer-wins)
POST
/rules/apply-all
W
Apply every active rule retroactively
PUT
/rules/{id}
W
Replace a rule
DELETE
/rules/{id}
W
Delete a rule
Annotations
Annotations are read-only via the transaction endpoint: GET /transactions/{id}/annotations above. Writes happen implicitly through comments, tag adds/removes, category changes, and rule applications — never directly.
Comments
See the Transactions table — comments are nested under /transactions/{transaction_id}/comments.
Mint a hosted-link URL — agent shares it, user opens in browser to run Plaid/Teller
POST
/connections/{id}/relink
W
Mint a re-auth hosted-link URL for one connection (always single-use)
GET
/connections/link/{id}
R
Poll a hosted-link session — status, result connection IDs
POST
/connections/csv/preview
W
Preview a CSV (multipart or JSON+base64) — no persist
POST
/connections/csv/import
W
Import a CSV — creates connection if absent, deduplicates by provider txn id
POST
/connections/plaid/link-token
W
Deprecated — use POST /providers/plaid/link-session. Returns a fresh Plaid Link token
POST
/connections/plaid/exchange
W
Deprecated — use POST /connections with provider: "plaid". Exchanges Plaid public_token
POST
/connections/teller
W
Deprecated — use POST /connections with provider: "teller". Registers from the Teller enrollment payload
Prefer POST /providers/{name}/link-session + POST /connections for new integrations — the OpenAPI spec treats them as canonical and the per-provider routes above are kept only as shims.
POST /connections/{id}/relink pins the new session to the connection in the path. The endpoint deliberately does not accept user_id or provider on the body — both are derived from the connection row, the session is always action="relink" and single_use=true, and re-auth against an already-disconnected connection returns 409 CONNECTION_DISCONNECTED.
Hosted-link page (token-scoped, page-internal)
These endpoints are called by the standalone /link/{token} page only. The token in the path is the credential — no API key required, no admin session, no rate limiter. They are intentionally not modeled in openapi.yaml (the spec covers the agent-facing /api/v1/* surface only); the drift test scopes itself to /api/v1/* and ignores this section.
Method
Path
Description
GET
/link/{token}
Standalone HTML page; user opens it to add a bank
GET
/_link/{token}/session
Redacted session view for the page (flips pending→active on first call)
POST
/_link/{token}/providers/{name}/start
Page-scoped start of a provider link session
GET
/_link/{token}/providers/teller/config
Public Teller bootstrap (application_id + environment); cert/key never exposed
POST
/_link/{token}/connections
Page-scoped connection create (attributes to session's user)
POST
/_link/{token}/reauth-complete
Page-scoped re-auth completion (only valid for action="relink" sessions; reactivates the pinned connection and burns the token)
The bearer middleware returns 401 INVALID_TOKEN for unknown tokens, 410 EXPIRED once past expires_at, 410 CONSUMED after /complete, and 410 GONE for any other terminal state. Scope-pinning lives in each handler: a session minted with provider="plaid" will 403 FORBIDDEN any /_link/.../providers/teller/start or /_link/.../connections body that names a different provider.
Sync
Method
Path
Scope
Description
POST
/sync
W
Trigger sync — all active connections, or one via {"connection_id": "..."}
GET
/sync/logs
R
Paginated history; filters connection_id, status, trigger, from, to
GET
/sync/logs/{id}
R
Single log + per-account rows
GET
/sync/health
R
Aggregate stats (success rate, p50 duration)
GET
/sync/health/providers
R
Per-provider stats
GET
/sync/stats
R
Totals matching the same filter set as /sync/logs
Users
Method
Path
Scope
Description
GET
/users
R
List household members
GET
/users/{id}
R
Single member
POST
/users
W
Create a household member
PATCH
/users/{id}
W
Update name / email
DELETE
/users/{id}
W
Delete (blocked by FK; hit /wipe-data first if needed)
POST
/users/{id}/wipe-data
W
Destructive. Removes all rows attributed to this user
Login accounts (sensitive — all write-scope)
Method
Path
Scope
Description
GET
/users/{user_id}/login
W
List logins for the user
POST
/users/{user_id}/login
W
Create a login. Response includes plaintext setup_token (one-time only)
Empty sensitive fields on PUT preserve the stored value (so you can update webhook_url without retyping the secret). All sensitive fields are AES-256-GCM encrypted at rest.
App config
Method
Path
Scope
Description
GET
/config
W
List every app_config row with effective source (env / db / default); secret values are masked unless ?reveal=true
GET
/config/{key}
W
Get a single config value; same masking and ?reveal=true semantics
PUT
/config/{key}
W
Write a value into the app_config table (body: {"value":"..."})
DELETE
/config/{key}
W
Drop the row (effective value falls back to env or compile-in default)
Secret-flagged keys are masked on read. A denylist of keys (ENCRYPTION_KEY, teller_cert_pem, teller_key_pem) may never be revealed and refuse writes via this surface — manage them through env vars or the admin UI.
Webhook events
Method
Path
Scope
Description
GET
/webhook-events
W
Paginated list of recent webhook events; filters provider, status, page, limit
POST
/webhook-events/{id}/replay
W
Re-trigger the manual sync the event would have caused; events without a connection are reported as triggered: false
Workflows
Workflows are scheduled Claude Agent SDK runs that call breadbox MCP to enrich, categorize, or report on data (the REST surface for the Workflows product; renamed from /agents/*). Runs are append-only (no delete). Settings carry Anthropic credentials (encrypted at rest) and global caps.
Method
Path
Scope
Description
GET
/workflows
R
List all agent definitions with last_run inlined (bare JSON array)
GET
/workflows/{slug}
R
One definition; accepts slug, short_id, or UUID
POST
/workflows
W
Create an agent definition
PATCH
/workflows/{slug}
W
PATCH-merge update; omitted fields are unchanged
DELETE
/workflows/{slug}
W
Delete the definition; historical runs preserved (FK SET NULL)
POST
/workflows/{slug}/enable
W
Flip enabled=true
POST
/workflows/{slug}/disable
W
Flip enabled=false
POST
/workflows/{slug}/run
W
Trigger an immediate synchronous run; optional body fields {prompt_prefix} (prepend, ≤2000) and {prompt} (full override, ≤40000) — prompt wins when both set; 503 CONCURRENCY_LOCKED when another run is in progress
POST
/workflows/test
W
Run the diagnostic smoke test (tiny "say OK" prompt, no MCP servers, ~5¢ cap); 422 AUTH_NOT_CONFIGURED / AGENT_BINARY_NOT_FOUND
POST
/workflows/cleanup
W
Run the agent cleanup pass on demand; returns {runs_deleted, transcripts_deleted, transcripts_scanned, retention_days, transcript_dir}
GET
/workflows/{slug}/runs
R
Offset-paginated run history; ?limit=50&offset=0 (max 200); filters: status, trigger, hit_cap (max_turns/max_budget/any), start, end
GET
/workflows/runs
R
Cross-agent run history with agent_slug+agent_name per row; same filters as /workflows/{slug}/runs plus optional agent=<slug> to narrow to one definition
GET
/workflow-presets
R
List the code-defined Workflow preset gallery, annotated with enabled-state
POST
/workflow-presets/{slug}/enable
W
Instantiate a workflow from a preset (409 if already enabled)
GET
/workflows/runs/recent-errors
R
Errored runs across all agents in the last hours (default 24, max 168); ?limit=5 (max 50); joined with agent_slug+agent_name for deep-link
GET
/workflows/runs/{shortId}
R
One run detail (by short_id or UUID)
PATCH
/workflows/runs/{shortId}
W
Set/clear the operator note on a run. Body { "note": "..." }; empty string clears. Capped at 2000 chars
GET
/workflows/runs/{shortId}/transcript
R
Streams the NDJSON transcript; 404 when not yet written
GET
/workflows/settings
R
Agent subsystem config; token fields returned masked, never plaintext
GET
/workflows/status
R
Cheap readiness probe — {auth_configured, binary_present, ready} for onboarding hints (no API call)
PUT
/workflows/settings
W
Update settings; nil fields are unchanged, empty string for token fields clears them
subscription_token and anthropic_api_key are AES-256-GCM encrypted at rest. GET returns a masked display string ("sk-ant-oat01-XXXXXXXXX••••wxyz"); the full value never leaves the server. A per-run scoped API key (actor_type='agent') is minted at run start by the orchestrator and revoked at completion — it is not exposed via this surface.
Rate limiting
All /api/v1/* routes (except /health/* and /api/v1/version) are rate-limited per API key. Defaults: 120 req/min, burst 60. Env vars API_RATE_LIMIT_RPM / API_RATE_LIMIT_BURST. Over-limit responses: 429 RATE_LIMITED with X-RateLimit-Limit/Remaining/Reset + Retry-After.