API reference
Generated from the OpenAPI schema (app v0.2.0) by tools/gen_api_reference.py — do not edit by hand.
Auth: management endpoints (/api/*) take Authorization: Bearer fnp_… (personal access token, minted under avatar → API tokens). The public runtime (/v1/*) takes a per-function invoke key (fnk_…). Python SDK: fnai.Client (management) / fnai.Function (runtime).
New to the API? Start with the SDK quickstart; credential kinds are explained in sdk-auth.md.
functions
Section titled “functions”Functions, versions (drafts; frozen on deploy), playground.
GET /api/functions
Section titled “GET /api/functions”list_functions — List Functions
| parameter | in | type | required |
|---|---|---|---|
include_archived |
query | boolean | no |
Returns: list of FunctionOut
POST /api/functions
Section titled “POST /api/functions”create_function — Create Function
Request body: FunctionCreate
Returns: FunctionOut
DELETE /api/functions/{fn_id}
Section titled “DELETE /api/functions/{fn_id}”archive_function — Archive Function
Archive, not hard delete — versions, experiments, and traces are evidence and stay.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
GET /api/functions/{fn_id}
Section titled “GET /api/functions/{fn_id}”get_function — Get Function
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: FunctionOut
PATCH /api/functions/{fn_id}
Section titled “PATCH /api/functions/{fn_id}”patch_function — Patch Function
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: FunctionPatch
Returns: FunctionOut
GET /api/functions/{fn_id}/editors
Section titled “GET /api/functions/{fn_id}/editors”list_function_editors — List Function Editors
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of FunctionEditorOut
POST /api/functions/{fn_id}/editors
Section titled “POST /api/functions/{fn_id}/editors”grant_function_editor — Grant Function Editor
Only someone who already holds blanket functions.write may delegate a narrower slice of it to someone else.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: FunctionEditorIn
Returns: FunctionEditorOut
DELETE /api/functions/{fn_id}/editors/{user_id}
Section titled “DELETE /api/functions/{fn_id}/editors/{user_id}”revoke_function_editor — Revoke Function Editor
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
user_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
GET /api/functions/{fn_id}/types
Section titled “GET /api/functions/{fn_id}/types”function_types — Function Types
fn types — caller-side types (TS interface / Python TypedDict) for the
function’s contract. Prefers the ACTIVE deployment’s version (exactly what
/v1 validates against), else the latest version, else fn-level schemas.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
lang |
query | string | no |
Returns: TypesOut
GET /api/functions/{fn_id}/versions
Section titled “GET /api/functions/{fn_id}/versions”list_versions — List Versions
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of VersionOut
POST /api/functions/{fn_id}/versions
Section titled “POST /api/functions/{fn_id}/versions”create_version — Create Version
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: VersionCreate
Returns: VersionOut
GET /api/me
Section titled “GET /api/me”me — Me
Returns: MeOut
PUT /api/me/default-space
Section titled “PUT /api/me/default-space”set_default_space — Set Default Space
Personal preference — where this user lands on sign-in. Any member may set their own; the space must belong to the current account.
Request body: DefaultSpaceSet
Returns: DefaultSpaceOut
GET /api/overview
Section titled “GET /api/overview”overview — Overview
Scope-aware landing data. Account-level roles (or platform admin) get per-space aggregates; space-level users get their space’s stats only. Aggregation temporarily re-points the RLS space GUC per space — legitimate, because the space list itself is derived from the user’s account rights.
Archived spaces are excluded by default (SMA-192) — include_archived=true is how the Spaces pane’s “Archived” section and Restore action see them.
| parameter | in | type | required |
|---|---|---|---|
include_archived |
query | boolean | no |
Returns: OverviewOut
POST /api/platform/accounts
Section titled “POST /api/platform/accounts”create_account — Create Account
Register a customer organization: the tenant, its first space, and an account_admin invitation for the owner. This replaces creating orgs by running scripts/seed_*.py. Platform staff only.
Nothing is emailed — the raw invite token comes back in this response for the operator to pass on (see members/invites.py).
Request body: AccountCreate
Returns: AccountCreatedOut
PATCH /api/platform/accounts/{account_id}/subscription
Section titled “PATCH /api/platform/accounts/{account_id}/subscription”update_subscription — Update Subscription
SMA-282. Platform staff only, V1 — no self-serve. An org’s plan,
status, and credit allowance/billing-period-start (both null until set
here) live directly on Tenant; billing (this epic) is now their writer,
per PERMISSIONS-ARCHITECTURE.md §3’s plan for how RBAC will read plan.
| parameter | in | type | required |
|---|---|---|---|
account_id |
path | string | yes |
Request body: SubscriptionUpdate
Returns: SubscriptionOut
GET /api/platform/overview
Section titled “GET /api/platform/overview”platform_overview — Platform Overview
Vendor dashboard: every org with spaces/users/functions/experiments/spend. Aggregates by re-pointing the RLS GUCs per tenant+space — platform staff have no ambient data access; this endpoint is their explicit, guarded window.
Returns: JSON (shape documented in the handler)
GET /api/platform/prompts
Section titled “GET /api/platform/prompts”list_platform_prompts — List Platform Prompts
Returns: list of PromptBriefOut
POST /api/platform/prompts
Section titled “POST /api/platform/prompts”create_platform_prompt — Create Platform Prompt
Request body: JSON object
Returns: PromptCreatedOut
GET /api/platform/users
Section titled “GET /api/platform/users”platform_users — Platform Users
Vendor console: every user with their org/space memberships. Filterable by org name and free-text (email/name). Platform staff only.
| parameter | in | type | required |
|---|---|---|---|
account |
query | any | no |
q |
query | any | no |
Returns: JSON (shape documented in the handler)
POST /api/spaces
Section titled “POST /api/spaces”create_space — Create Space
First-run onboarding: an org admin creates a space in their account. Account-scoped admin roles cover every space in the account, so no per-space membership is minted here.
Request body: SpaceCreate
Returns: SpaceCreatedOut
DELETE /api/spaces/{space_id}
Section titled “DELETE /api/spaces/{space_id}”archive_space — Archive Space
Archive, not hard delete — same shape as functions/datasets. Running deployments and traffic are untouched; the space just leaves the default list (SMA-192 v1 does not add a liveness check before allowing this).
| parameter | in | type | required |
|---|---|---|---|
space_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
PATCH /api/spaces/{space_id}
Section titled “PATCH /api/spaces/{space_id}”update_space — Update Space
| parameter | in | type | required |
|---|---|---|---|
space_id |
path | string | yes |
Request body: SpaceUpdate
Returns: SpaceCreatedOut
POST /api/spaces/{space_id}/restore
Section titled “POST /api/spaces/{space_id}/restore”restore_space — Restore Space
| parameter | in | type | required |
|---|---|---|---|
space_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
GET /api/versions/{v_id}
Section titled “GET /api/versions/{v_id}”get_version — Get Version
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Returns: VersionOut
PUT /api/versions/{v_id}
Section titled “PUT /api/versions/{v_id}”update_version — Update Version
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Request body: VersionUpdate
Returns: VersionOut
GET /api/versions/{v_id}/artifact
Section titled “GET /api/versions/{v_id}/artifact”get_artifact — Get Artifact
The compiled artifact — the exact executable form; also the future export bundle. Compiles on demand for versions saved before the compiler existed (drafts persist it).
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
scores
Section titled “scores”Per-function score library (checks/judges) and gate flags.
GET /api/functions/{fn_id}/scores
Section titled “GET /api/functions/{fn_id}/scores”list_scores — List Scores
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of ScoreOut
POST /api/functions/{fn_id}/scores
Section titled “POST /api/functions/{fn_id}/scores”create_score — Create Score
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: ScoreCreate
Returns: ScoreOut
DELETE /api/scores/{score_id}
Section titled “DELETE /api/scores/{score_id}”delete_score — Delete Score
| parameter | in | type | required |
|---|---|---|---|
score_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
planner
Section titled “planner”Measurement catalogue, plans, gate → plan → accept.
GET /api/catalogue
Section titled “GET /api/catalogue”get_catalogue — Get Catalogue
The measurement catalogue as data — the browser groups these by dimension.
Returns: list of CatalogueEntryOut
GET /api/functions/{fn_id}/plans
Section titled “GET /api/functions/{fn_id}/plans”list_plans — List Plans
Runnable plans (scorings) for a function — a plan is a named collection of
checks that an experiment can pin. Only hand-built (manual) and accepted rows
qualify: gated/planned rows are the readiness pipeline’s working records
for a version, never scorings, so they are excluded rather than shown as empty
“Plan” entries. Returns plans with materialised checks first.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of PlanBriefOut
POST /api/functions/{fn_id}/plans
Section titled “POST /api/functions/{fn_id}/plans”create_manual_plan — Create Manual Plan
A hand-built plan: an empty named collection of checks (add checks via the browser).
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: JSON object
Returns: PlanCreatedOut
DELETE /api/plans/{plan_id}
Section titled “DELETE /api/plans/{plan_id}”delete_plan — Delete Plan
Remove a plan and its checks (accept is non-idempotent, so duplicates happen). Checks referenced by past experiment results are detached into the library, not deleted.
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Returns: DeletePlanOut
PATCH /api/plans/{plan_id}
Section titled “PATCH /api/plans/{plan_id}”rename_plan — Rename Plan
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Request body: JSON object
Returns: PlanCreatedOut
POST /api/plans/{plan_id}/accept
Section titled “POST /api/plans/{plan_id}/accept”accept_plan — Accept Plan
Turn the SELECTED proposed checks into Eval rows the harness will execute.
body.indices picks a subset of plan.checks (all of them if omitted). Blocking
checks become gates. Idempotency is the caller’s concern — re-accepting adds again.
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Request body: JSON object
Returns: AcceptPlanOut
POST /api/plans/{plan_id}/clone
Section titled “POST /api/plans/{plan_id}/clone”clone_plan — Clone Plan
Copy a plan and its scores into a new named plan.
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Request body: JSON object
Returns: PlanCreatedOut
GET /api/plans/{plan_id}/scores
Section titled “GET /api/plans/{plan_id}/scores”plan_scores — Plan Scores
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Returns: list of ScoreOut
POST /api/plans/{plan_id}/scores
Section titled “POST /api/plans/{plan_id}/scores”add_plan_score — Add Plan Score
Add a score to a plan from the catalogue. body: {check, target, gate, params:{…}}.
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
Request body: JSON object
Returns: ScoreOut
DELETE /api/plans/{plan_id}/scores/{score_id}
Section titled “DELETE /api/plans/{plan_id}/scores/{score_id}”remove_plan_score — Remove Plan Score
| parameter | in | type | required |
|---|---|---|---|
plan_id |
path | integer | yes |
score_id |
path | integer | yes |
Returns: RemovePlanScoreOut
POST /api/versions/{v_id}/gate
Section titled “POST /api/versions/{v_id}/gate”run_gate — Run Gate
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Returns: PlanOut
GET /api/versions/{v_id}/plan
Section titled “GET /api/versions/{v_id}/plan”get_plan — Get Plan
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Returns: PlanOut
POST /api/versions/{v_id}/plan
Section titled “POST /api/versions/{v_id}/plan”run_plan — Run Plan
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Request body: JSON object
Returns: PlanOut
experiments
Section titled “experiments”Experiments over versions × models × repeats; findings.
DELETE /api/experiments/{exp_id}
Section titled “DELETE /api/experiments/{exp_id}”delete_experiment — Delete Experiment
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
GET /api/experiments/{exp_id}
Section titled “GET /api/experiments/{exp_id}”get_experiment — Get Experiment
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: ExperimentOut
PATCH /api/experiments/{exp_id}
Section titled “PATCH /api/experiments/{exp_id}”patch_experiment — Patch Experiment
Rename and/or edit pins. Only drafts (queued, not yet run) may change pins.
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Request body: ExperimentPatch
Returns: ExperimentOut
POST /api/experiments/{exp_id}/cancel
Section titled “POST /api/experiments/{exp_id}/cancel”cancel_experiment — Cancel Experiment
Stop a running experiment after its current call. The row flips to failed (no results); the runner notices between rollouts and aborts. Edit / run again afterwards — nothing to clone.
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: ExperimentOut
POST /api/experiments/{exp_id}/clone
Section titled “POST /api/experiments/{exp_id}/clone”clone_experiment — Clone Experiment
Copy pins into a new draft. Finished experiments stay immutable.
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: ExperimentOut
GET /api/experiments/{exp_id}/findings
Section titled “GET /api/experiments/{exp_id}/findings”list_findings — List Findings
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: list of FindingOut
POST /api/experiments/{exp_id}/run
Section titled “POST /api/experiments/{exp_id}/run”run_saved_experiment — Run Saved Experiment
Run a draft experiment — never run, or failed without results (a retry). Completed experiments are immutable: clone to run again.
| parameter | in | type | required |
|---|---|---|---|
exp_id |
path | integer | yes |
Returns: ExperimentOut
POST /api/findings/{finding_id}/add-score
Section titled “POST /api/findings/{finding_id}/add-score”add_score_from_finding — Add Score From Finding
| parameter | in | type | required |
|---|---|---|---|
finding_id |
path | integer | yes |
Returns: ScoreOut
POST /api/functions/import/one-shot-agent
Section titled “POST /api/functions/import/one-shot-agent”import_one_shot_agent — Import One Shot Agent
Request body: ImportRequest
Returns: ImportResultOut
GET /api/functions/{fn_id}/experiments
Section titled “GET /api/functions/{fn_id}/experiments”list_experiments — List Experiments
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of ExperimentOut
POST /api/functions/{fn_id}/experiments
Section titled “POST /api/functions/{fn_id}/experiments”create_experiment — Create Experiment
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: ExperimentCreate
Returns: ExperimentOut
GET /api/functions/{fn_id}/score-results
Section titled “GET /api/functions/{fn_id}/score-results”score_results — Score Results
Flexible cross-experiment view over the raw score-results fact table.
Default grain: one row per (version × model × temperature × score) aggregated across ALL of the function’s experiments — mean / std / pass_rate / n — so the client can rank, pivot and filter however it likes. RLS scopes every joined table to the caller’s space. Optional ?score=NAME narrows to one score for a per-score leaderboard.
Comparison controls:
- ?experiment_ids=1,2 restricts the aggregation to those experiments;
- ?group_by=experiment adds experiment identity to the grain, so “score X in experiment A vs experiment B” is answerable side by side.
Caveat: the default grain mixes inputs across experiments, so it isolates
model/version only when those experiments shared inputs — n and experiments
are returned so the caller can judge comparability.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
score |
query | any | no |
experiment_ids |
query | any | no |
group_by |
query | any | no |
Returns: list of ScoreResultOut
GET /api/tools
Section titled “GET /api/tools”list_tools — List Tools
Returns: list of ToolInfoOut
POST /api/versions/{v_id}/playground/run
Section titled “POST /api/versions/{v_id}/playground/run”playground_run — Playground Run
| parameter | in | type | required |
|---|---|---|---|
v_id |
path | integer | yes |
Request body: PlaygroundRun
Returns: PlaygroundOut
datasets
Section titled “datasets”Datasets, rows, snapshots, uploads.
DELETE /api/datasets/{ds_id}
Section titled “DELETE /api/datasets/{ds_id}”delete_dataset — Delete Dataset
Hard delete — only when no experiment pins a snapshot of this dataset; archive otherwise.
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
PATCH /api/datasets/{ds_id}
Section titled “PATCH /api/datasets/{ds_id}”patch_dataset — Patch Dataset
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Request body: DatasetPatch
Returns: DatasetOut
GET /api/datasets/{ds_id}/rows
Section titled “GET /api/datasets/{ds_id}/rows”list_rows — List Rows
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Returns: list of RowOut
POST /api/datasets/{ds_id}/rows
Section titled “POST /api/datasets/{ds_id}/rows”add_row — Add Row
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Request body: RowIn
Returns: RowOut
POST /api/datasets/{ds_id}/snapshot
Section titled “POST /api/datasets/{ds_id}/snapshot”snapshot_dataset — Snapshot Dataset
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Request body: JSON object
Returns: SnapshotOut
GET /api/datasets/{ds_id}/snapshots
Section titled “GET /api/datasets/{ds_id}/snapshots”list_snapshots — List Snapshots
Version history: every frozen snapshot, newest first, with the experiments pinning it.
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Returns: list of SnapshotHistoryOut
DELETE /api/datasets/{ds_id}/snapshots/{snap_id}
Section titled “DELETE /api/datasets/{ds_id}/snapshots/{snap_id}”delete_snapshot — Delete Snapshot
Drop one frozen version — only while no experiment has used it. An experiment that pins a version would otherwise lose the very rows it was measured against, so a used version is kept for good: same rule the dataset delete enforces, one version at a time.
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
snap_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
GET /api/datasets/{ds_id}/snapshots/{snap_id}
Section titled “GET /api/datasets/{ds_id}/snapshots/{snap_id}”get_snapshot — Get Snapshot
One version’s frozen rows — what the history list leaves out on purpose.
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
snap_id |
path | integer | yes |
Returns: SnapshotDetailOut
POST /api/datasets/{ds_id}/upload
Section titled “POST /api/datasets/{ds_id}/upload”upload_rows — Upload Rows
Bulk-load rows from a spreadsheet. Mapping is explicit — columns are never guessed. Values are coerced to the types declared by the function’s input schema (latest version’s schema, falling back to the function-level one).
| parameter | in | type | required |
|---|---|---|---|
ds_id |
path | integer | yes |
Request body: JSON object
Returns: UploadResultOut
GET /api/functions/{fn_id}/datasets
Section titled “GET /api/functions/{fn_id}/datasets”list_datasets — List Datasets
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
include_archived |
query | boolean | no |
Returns: list of DatasetOut
POST /api/functions/{fn_id}/datasets
Section titled “POST /api/functions/{fn_id}/datasets”create_dataset — Create Dataset
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: DatasetCreate
Returns: DatasetOut
DELETE /api/rows/{row_id}
Section titled “DELETE /api/rows/{row_id}”delete_row — Delete Row
| parameter | in | type | required |
|---|---|---|---|
row_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
PATCH /api/rows/{row_id}
Section titled “PATCH /api/rows/{row_id}”update_row — Update Row
| parameter | in | type | required |
|---|---|---|---|
row_id |
path | integer | yes |
Request body: RowUpdate
Returns: RowOut
deployments
Section titled “deployments”Deploy/rollback/history + per-function invoke keys (fnk_*).
POST /api/functions/{fn_id}/deploy
Section titled “POST /api/functions/{fn_id}/deploy”deploy — Deploy
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: JSON object
Returns: DeploymentOut
GET /api/functions/{fn_id}/deployment
Section titled “GET /api/functions/{fn_id}/deployment”current_deployment — Current Deployment
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: DeploymentOut
GET /api/functions/{fn_id}/deployments
Section titled “GET /api/functions/{fn_id}/deployments”deployment_history — Deployment History
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of DeploymentOut
GET /api/functions/{fn_id}/invoke-keys
Section titled “GET /api/functions/{fn_id}/invoke-keys”list_api_keys — List Api Keys
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Returns: list of InvokeKeyOut
POST /api/functions/{fn_id}/invoke-keys
Section titled “POST /api/functions/{fn_id}/invoke-keys”create_api_key — Create Api Key
Mints fnk_<32 hex>; the plaintext appears ONCE in this response and is
never retrievable again — only its SHA-256 is stored.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: JSON object
Returns: InvokeKeyMintedOut
POST /api/functions/{fn_id}/rollback
Section titled “POST /api/functions/{fn_id}/rollback”rollback — Rollback
Re-point to a prior version. Default target: the most recently deployed version different from the current one; or pass {version_id} explicitly.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
Request body: JSON object
Returns: DeploymentOut
DELETE /api/invoke-keys/{key_id}
Section titled “DELETE /api/invoke-keys/{key_id}”revoke_api_key — Revoke Api Key
| parameter | in | type | required |
|---|---|---|---|
key_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
traces
Section titled “traces”Trace explorer, function health, flag-to-dataset flywheel.
GET /api/functions/{fn_id}/health
Section titled “GET /api/functions/{fn_id}/health”function_health — Function Health
The ‘is my function okay?’ rollup, computed from traces in the window: calls, error rate, p50/p95 latency, spend — total and per source/version.
| parameter | in | type | required |
|---|---|---|---|
fn_id |
path | integer | yes |
window_hours |
query | integer | no |
Returns: HealthOut
GET /api/traces
Section titled “GET /api/traces”list_traces — List Traces
| parameter | in | type | required |
|---|---|---|---|
function_id |
query | any | no |
source |
query | any | no |
limit |
query | integer | no |
Returns: list of TraceSummaryOut
GET /api/traces/{trace_id}
Section titled “GET /api/traces/{trace_id}”get_trace — Get Trace
| parameter | in | type | required |
|---|---|---|---|
trace_id |
path | string | yes |
Returns: TraceDetailOut
POST /api/traces/{trace_id}/flag
Section titled “POST /api/traces/{trace_id}/flag”flag_trace — Flag Trace
The flywheel button: a (bad) production output becomes a regression-test row. Lands in the function’s ‘{FLAG_DATASET_NAME}’ dataset (auto-created), input preserved verbatim, expected left EMPTY — the user fills in what the output SHOULD have been.
| parameter | in | type | required |
|---|---|---|---|
trace_id |
path | string | yes |
Returns: TraceFlagOut
Model price catalogue and spend summaries.
GET /api/costs/catalogue
Section titled “GET /api/costs/catalogue”get_catalog — Get Catalog
Returns: list of PriceOut
GET /api/costs/summary
Section titled “GET /api/costs/summary”get_summary — Get Summary
Spend for the current scope (RLS does the filtering): totals + per model/source.
Returns: CostSummaryOut
BYOK provider keys (org/space scope) — model calls run on these.
GET /api/provider-keys
Section titled “GET /api/provider-keys”list_keys — List Keys
Returns: list of KeyOut
POST /api/provider-keys
Section titled “POST /api/provider-keys”create_key — Create Key
Request body: KeyCreate
Returns: KeyOut
GET /api/provider-keys/console
Section titled “GET /api/provider-keys/console”keys_console — Keys Console
The org admin’s view: every key on the account, plus the spaces a key can be restricted to (a superset of the caller’s own spaces). Admins only.
Returns: ConsoleOut
DELETE /api/provider-keys/{key_id}
Section titled “DELETE /api/provider-keys/{key_id}”delete_key — Delete Key
| parameter | in | type | required |
|---|---|---|---|
key_id |
path | integer | yes |
Returns: JSON (shape documented in the handler)
PATCH /api/provider-keys/{key_id}
Section titled “PATCH /api/provider-keys/{key_id}”patch_key — Patch Key
| parameter | in | type | required |
|---|---|---|---|
key_id |
path | integer | yes |
Request body: KeyPatch
Returns: KeyOut
POST /api/provider-keys/{key_id}/rotate
Section titled “POST /api/provider-keys/{key_id}/rotate”rotate_key — Rotate Key
| parameter | in | type | required |
|---|---|---|---|
key_id |
path | integer | yes |
Request body: KeyRotate
Returns: KeyOut
POST /api/provider-keys/{key_id}/test
Section titled “POST /api/provider-keys/{key_id}/test”test_key — Test Key
| parameter | in | type | required |
|---|---|---|---|
key_id |
path | integer | yes |
Returns: KeyTestOut
Sign-in coordinates + personal access tokens (fnp_*).
GET /api/auth/config
Section titled “GET /api/auth/config”auth_config — Auth Config
Returns: AuthConfigOut
GET /api/auth/session
Section titled “GET /api/auth/session”session — Session
Where may this person go? Resolved from identity alone, before any tenant is bound — this is what the SPA calls to decide its landing page.
Deliberately NOT /api/me: that one describes the caller inside one already chosen account/space and 403s when the choice is wrong. This one has no choice to be wrong about.
Owners (platform_admin) get every organization; everyone else gets only the ones they hold a membership in. The auth tables carry no RLS policies, so that filter is the isolation boundary, not a convenience — widen it and every tenant name on the platform leaks to any authenticated caller.
Returns: SessionOut
GET /api/auth/tokens
Section titled “GET /api/auth/tokens”list_tokens — List Tokens
The caller’s own tokens — hashes never leave the store.
Returns: list of TokenOut
POST /api/auth/tokens
Section titled “POST /api/auth/tokens”mint_token — Mint Token
Mint a personal access token. The plaintext is in this response ONLY.
Request body: JSON object
Returns: TokenMintedOut
DELETE /api/auth/tokens/{token_id}
Section titled “DELETE /api/auth/tokens/{token_id}”revoke_token — Revoke Token
Revoke (keeps the row for audit). Own tokens only — others 404.
| parameter | in | type | required |
|---|---|---|---|
token_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
Public runtime surface (/v1) — invoke key auth, not PAT.
POST /v1/run/{fn_slug}
Section titled “POST /v1/run/{fn_slug}”run — Run
| parameter | in | type | required |
|---|---|---|---|
fn_slug |
path | string | yes |
Request body: JSON object
Returns: RunOut
GET /v1/run/{fn_slug}/contract
Section titled “GET /v1/run/{fn_slug}/contract”contract — Contract
Contract discovery for callers: the input/output schemas of the ACTIVE deployment — exactly what POST /v1/run validates against. Same auth and 404 semantics as the invoke path; the contract changes atomically with deploys and rollbacks.
| parameter | in | type | required |
|---|---|---|---|
fn_slug |
path | string | yes |
Returns: ContractOut
GET /api/audit
Section titled “GET /api/audit”list_audit_events — List Audit Events
function_id (SMA-54’s activity feed) narrows to the entity types that actually belong to one function — function/version/dataset/score/ experiment/deployment/plan/function_api_key — each filtered to that function, dropping key/trace/org/space entirely (see _FUNCTION_ID_COL).
SMA-342: platform_admin now writes inside every org, unconditionally audited — and unconditionally excluded here. This is the org-facing view (the only one that exists), so platform-actor rows never surface through it for anyone, platform staff included; the trail stays queryable directly against audit_log for internal debugging.
| parameter | in | type | required |
|---|---|---|---|
entity |
query | any | no |
function_id |
query | any | no |
before |
query | any | no |
limit |
query | integer | no |
Returns: list of AuditEventOut
GET /api/mcp/client-info
Section titled “GET /api/mcp/client-info”get_client_info — Get Client Info
Public, unauthenticated — SMA-263. Cognito has no Dynamic Client
Registration, so a real MCP client needs this pre-registered client_id
up front to skip DCR; the Connect AI Tools panel reads this to build a
working claude mcp add --client-id ... command.
Returns: McpClientInfoOut
DELETE /api/mcp/elevation
Section titled “DELETE /api/mcp/elevation”revoke_elevation — Revoke Elevation
Returns: McpElevationOut
GET /api/mcp/elevation
Section titled “GET /api/mcp/elevation”get_elevation — Get Elevation
Returns: McpElevationOut
POST /api/mcp/elevation
Section titled “POST /api/mcp/elevation”grant_elevation — Grant Elevation
Returns: McpElevationOut
GET /api/mcp/status
Section titled “GET /api/mcp/status”get_connection_status — Get Connection Status
No dedicated “connections” record exists (see ConnectAiToolsPanel’s own doc comment) – every MCP tool call already writes an mcp.{tool} AuditEvent (mcp/server.py:_call), so the most recent one for this user IS the connection status, with no new table or migration needed.
Returns: McpConnectionStatusOut
members
Section titled “members”POST /api/invitations/claim
Section titled “POST /api/invitations/claim”claim_pending — Claim Pending
Claim every pending invitation addressed to the signed-in email.
Cognito’s invitation email carries a temporary password but cannot carry a per-invitation link — the template is pool-wide. So an invitee who signs in with those credentials would otherwise land on a 403 with no membership. Here the verified email IS the proof, which is sound because the pool has AllowAdminCreateUserOnly=true: nobody can self-register as someone else, and Cognito verified the address before issuing the token.
Idempotent and safe to call on every sign-in — normally it claims nothing.
Returns: InvitationClaimOut
GET /api/invitations/{token}
Section titled “GET /api/invitations/{token}”preview_invitation — Preview Invitation
What the join page shows before sign-in: which org, which role, who for. Unauthenticated by design — holding the token is the proof.
| parameter | in | type | required |
|---|---|---|---|
token |
path | string | yes |
Returns: InvitationPreviewOut
POST /api/invitations/{token}/accept
Section titled “POST /api/invitations/{token}/accept”accept_invitation — Accept Invitation
Claim an invitation at first sign-in — this is where a pending membership
becomes a real one, and where the users row is minted if it doesn’t exist.
Runs outside get_scope on purpose: the claimant may have no users row yet,
which get_scope rejects with a 401 before we could ever create one.
| parameter | in | type | required |
|---|---|---|---|
token |
path | string | yes |
Returns: InvitationAcceptedOut
GET /api/members
Section titled “GET /api/members”list_members — List Members
Everyone with a membership in the caller’s organization — account-scoped members (every space) and space-scoped members alike.
Platform staff hold a platform-scoped membership, which matches neither
branch below, so Ferrous never shows up inside a customer’s member list.
Returns: list of MemberOut
GET /api/members/invitations
Section titled “GET /api/members/invitations”list_invitations — List Invitations
Invitations for this organization. Pending only unless include_closed.
| parameter | in | type | required |
|---|---|---|---|
include_closed |
query | boolean | no |
Returns: list of InvitationOut
POST /api/members/invitations
Section titled “POST /api/members/invitations”invite_member — Invite Member
Invite someone into this organization. space names a space to scope the
invitation to; omit it for account-wide access (every space).
The raw token is in this response ONLY — deliver it to the invitee. There is no mail transport yet.
Request body: InvitationCreate
Returns: InvitationMintedOut
DELETE /api/members/invitations/{invitation_id}
Section titled “DELETE /api/members/invitations/{invitation_id}”revoke_invitation — Revoke Invitation
Cancel a pending invitation. The row is kept, so the trail survives.
| parameter | in | type | required |
|---|---|---|---|
invitation_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
DELETE /api/members/{user_id}
Section titled “DELETE /api/members/{user_id}”deactivate_member — Deactivate Member
Remove this person’s access. Their users row and everything they built
stay — only the memberships are suspended, so the trail survives and the
access can be restored by re-inviting them.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
Returns: JSON (shape documented in the handler)
PATCH /api/members/{user_id}
Section titled “PATCH /api/members/{user_id}”update_member — Update Member
Set this person’s access: a tier (admin/member/viewer) and where it applies — the whole organization, or a named list of spaces.
Their memberships in this org are replaced wholesale, which is what keeps the model clean: one role per person per scope, no contradictory leftovers.
The owner is never a valid target here, including from themselves. Ordinary admins are structural equals — any one of them could otherwise demote any other, up to and including the person who registered the organization in the first place, and “whoever acts last wins” is not the same thing as “who the org actually recognizes as its owner”. See POST …/transfer-ownership, the one deliberate, atomic way ownership ever changes hands.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
Request body: MemberUpdate
Returns: MemberOut
GET /api/members/{user_id}/permissions
Section titled “GET /api/members/{user_id}/permissions”member_permissions — Member Permissions
What this person can do here, and where each right comes from — the role baseline separately from what was attached to them individually.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
Returns: MemberPermissionsOut
POST /api/members/{user_id}/permissions
Section titled “POST /api/members/{user_id}/permissions”grant_permission — Grant Permission
Attach one permission to this person, on top of their role.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
Request body: PermissionGrantIn
Returns: MemberPermissionsOut
DELETE /api/members/{user_id}/permissions/{permission}
Section titled “DELETE /api/members/{user_id}/permissions/{permission}”revoke_permission — Revoke Permission
Detach an individually granted permission. Role-derived rights are not touched here — change the role for those.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
permission |
path | string | yes |
space |
query | any | no |
Returns: JSON (shape documented in the handler)
POST /api/members/{user_id}/transfer-ownership
Section titled “POST /api/members/{user_id}/transfer-ownership”transfer_ownership — Transfer Ownership
Hand account_owner to another admin. Only the CURRENT owner may call this — members.write alone is not enough, an ordinary admin cannot do this to themselves, which is the entire point of the role existing.
The successor must already be an account_admin: ownership is not a way to smuggle someone into the organization, only a way to reassign a standing that already exists. Both membership rows are updated in one transaction — there is never a moment with zero owners or two.
| parameter | in | type | required |
|---|---|---|---|
user_id |
path | string | yes |
Returns: OwnershipTransferOut
GET /api/permissions
Section titled “GET /api/permissions”permission_catalogue — Permission Catalogue
Everything that can be granted, grouped by domain — what a role editor or per-user permission screen renders. Data, not a hard-coded UI list, so a new verb appears here the moment it is added to core/permissions.py.
Returns: PermissionCatalogueOut
GET /api/org
Section titled “GET /api/org”get_org — Get Org
Returns: OrgOut
PATCH /api/org
Section titled “PATCH /api/org”update_org — Update Org
Request body: OrgUpdate
Returns: OrgOut
untagged
Section titled “untagged”GET /api/health
Section titled “GET /api/health”health — Health
Returns: JSON (shape documented in the handler)