Skip to content

Latest commit

 

History

History
330 lines (265 loc) · 22.5 KB

File metadata and controls

330 lines (265 loc) · 22.5 KB

API endpoints

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
PATCH /accounts/{id} W Update display_name, is_excluded, is_dependent_linked, owner_user_id

Transactions

Method Path Scope Description
GET /transactions R Cursor-paginated list with filters; supports ?fields= selection
GET /transactions/count R Count matching the same filters
GET /transactions/summary R Aggregates by category / month / week / day
GET /transactions/merchants R Merchant-level stats (count, total, avg)
GET /transactions/{id} R Single transaction
GET /transactions/{id}/annotations R Activity-timeline rows; mirrors MCP list_annotations
GET /transactions/{transaction_id}/comments R List comments on a transaction
POST /transactions/update W Atomic multi-field batch (category + tags + comment per row, max 50)
POST /transactions/batch-categorize W Set category on many transactions (max 500)
POST /transactions/bulk-recategorize W Server-side recategorize by filter
PATCH /transactions/{id}/category W Set category (category_id; last-writer-wins)
DELETE /transactions/{id}/category W Reset category to provider default
DELETE /transactions/{id} W Soft-delete (sets deleted_at)
POST /transactions/{id}/restore W Restore a soft-deleted transaction
POST /transactions/{id}/tags W Attach a tag to a transaction
DELETE /transactions/{id}/tags/{slug} W Detach a tag from a transaction
PATCH /transactions/{id}/metadata/{key} W Upsert one free-form metadata key
DELETE /transactions/{id}/metadata/{key} W Remove one metadata key
PUT /transactions/{id}/metadata W Replace the entire metadata object
DELETE /transactions/{id}/metadata W Clear metadata to {}
POST /transactions/{id}/flag W Flag for attention (optional reason → comment)
DELETE /transactions/{id}/flag W Clear the flag
POST /transactions/{transaction_id}/comments W Add a comment
PUT /transactions/{transaction_id}/comments/{id} W Edit a comment
DELETE /transactions/{transaction_id}/comments/{id} W Delete a comment

Categories

Method Path Scope Description
GET /categories R List all categories
GET /categories/{id} R Single category
GET /categories/export R Export as TSV
POST /categories W Create a category
POST /categories/import W Import categories from TSV
POST /categories/{id}/merge W Merge one category into another (transactions migrate, source removed)
PUT /categories/{id} W Replace category
DELETE /categories/{id} W Delete (blocked if any transactions reference it; clear them first)

Tags

Method Path Scope Description
GET /tags R List tag vocabulary
GET /tags/{slug} R Single tag (accepts UUID, short_id, or slug)
POST /tags W Create a tag (slug must match ^[a-z0-9][a-z0-9\-:]*[a-z0-9]$)
PATCH /tags/{slug} W Partial update of display_name/description/color/icon/lifecycle
DELETE /tags/{slug} W Delete a tag

Subscriptions (recurring series)

Method Path Scope Description
GET /series R List recurring series (thin: id, short_id, name, type, tags)
GET /series/{id} R Single series (accepts UUID or short_id)
POST /series W Assign by series_id, or mint/resolve by series_name+create_if_missing (optional type); links transaction_ids (≤50, NULL-fill only)
POST /series/{id}/transactions W Link transactions (≤50, NULL-fill only) to a series
DELETE /series/{id}/transactions/{txid} W Unlink a transaction from a series; errors if not a member
PATCH /series/{id} W Edit a thin series' name and/or type (subscription/bill/loan/other)

Counterparties

Method Path Scope Description
GET /counterparties R List counterparties (id, short_id, name, enrichment fields)
GET /counterparties/{id} R Single counterparty (accepts UUID or short_id)
POST /counterparties W Create by name + optional website_url/logo_url/category_id/mcc; duplicate live name rejected
PATCH /counterparties/{id} W Enrich name/website_url/logo_url/category_id/mcc (partial; empty body rejected)
POST /counterparties/{id}/transactions W Link transactions (≤50, NULL-fill only) to a counterparty
DELETE /counterparties/{id}/transactions/{txid} W Unlink a transaction; errors if not a current member

Rules

Method Path Scope Description
GET /rules R Cursor-paginated list
GET /rules/{id} R Single rule
GET /rules/{id}/sync-history R Last N sync runs that triggered this rule
POST /rules W Create a rule
POST /rules/batch W Bulk create; per-op results, on_error: continue|abort
POST /rules/preview W Dry-run — show which transactions would match
POST /rules/{id}/apply W 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.

Connections

Method Path Scope Description
GET /connections R List active connections
GET /connections/{id} R Connection detail (provider, status, paused, sync interval, account count)
GET /connections/{id}/status R Status + last sync info
GET /providers R List installed providers ({name, configured, needs_link_session})
GET /providers/{name} R Single provider entry — capability flags for one provider
POST /providers/{name}/link-session W Start a link/auth session — returns a provider token when needed; 204 when no link step
POST /providers/{name}/test W Round-trip a credentials check; returns {ok, message}
DELETE /providers/{name} W Disable a provider — clears credentials from app_config, drops the live provider entry
POST /connections W Create from a provider payload (provider discriminator: plaid, teller)
POST /connections/{id}/sync W Trigger sync for one connection (202; runs async)
POST /connections/{id}/paused W Pause or resume scheduled syncs
POST /connections/{id}/reauth W Start re-auth flow; returns a fresh link token
POST /connections/{id}/reauth-complete W Mark connection active again after the user finishes re-auth
DELETE /connections/{id} W Soft-disconnect (wipes encrypted tokens, transactions soft-deleted)
POST /connections/link W 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)
POST /_link/{token}/complete User-initiated "I'm done"; consumes the token (idempotent — already-completed returns 204)
POST /_link/{token}/fail Page reports a provider-side SDK failure

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)
PATCH /users/{user_id}/login/{login_id} W Update role
DELETE /users/{user_id}/login/{login_id} W Remove a login
POST /users/{user_id}/login/{login_id}/regenerate-token W Issue a fresh setup token
GET /login-accounts W List every login account (flat — no parent user_id)
DELETE /login-accounts/{id} W Delete a login by its own id
POST /login-accounts/{id}/reset-password W Issue a fresh setup token (flat alias of regenerate-token)

Account links (dependent-account mirroring)

Method Path Scope Description
GET /account-links R List links
GET /account-links/{id} R Single link
GET /account-links/{id}/matches R Cursor-paginated matched-transaction list
POST /account-links W Create a link between a primary and dependent account
PUT /account-links/{id} W Update link
DELETE /account-links/{id} W Delete link
POST /account-links/{id}/reconcile W Re-run matcher across both accounts
POST /transaction-matches/{id}/confirm W Confirm a candidate match
POST /transaction-matches/{id}/reject W Reject a candidate match
POST /transaction-matches/manual W Create a manual match between two transactions

Reports

Method Path Scope Description
GET /reports R List agent reports
GET /reports/unread-count R Unread badge count
GET /reports/{id} R Single report
POST /reports W Create a report (typically from an agent)
PATCH /reports/{id}/read W Mark report read
PATCH /reports/{id}/unread W Mark report unread
POST /reports/read-all W Mark every report read
DELETE /reports/{id} W Delete a report

API keys (write-scope only — listing is sensitive)

Method Path Scope Description
GET /api-keys W List keys (hashed prefixes only — never plaintext)
POST /api-keys W Create. Response includes plaintext key once. Accepts optional actor_type (user/agent/system, default agent) and actor_name
DELETE /api-keys/{id} W Soft-revoke a key
GET /keys/me R Whoami: return the calling key's id, prefix, scope, actor_type, actor_name

Provider settings

Method Path Scope Description
GET /settings/providers R Redacted view ({configured, secret_set, certificate_set}) — never raw secrets
PUT /settings/providers/plaid W Set/update Plaid client_id, secret, environment, webhook_url
PUT /settings/providers/teller W Set/update Teller application_id, cert + private key PEM

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.

Error envelope

Every error response uses:

{ "error": { "code": "UPPER_SNAKE_CASE", "message": "..." } }

Common codes: VALIDATION_ERROR, INVALID_PARAMETER, NOT_FOUND, UNAUTHORIZED, FORBIDDEN, INSUFFICIENT_SCOPE, RATE_LIMITED, CONFLICT, PROVIDER_ERROR, INTERNAL_ERROR. Codes are stable contracts — never rename.

See also