Skip to content

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, versions (drafts; frozen on deploy), playground.

list_functions — List Functions

parameter in type required
include_archived query boolean no

Returns: list of FunctionOut

create_function — Create Function

Request body: FunctionCreate

Returns: FunctionOut

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_function — Get Function

parameter in type required
fn_id path integer yes

Returns: FunctionOut

patch_function — Patch Function

parameter in type required
fn_id path integer yes

Request body: FunctionPatch

Returns: FunctionOut

list_function_editors — List Function Editors

parameter in type required
fn_id path integer yes

Returns: list of FunctionEditorOut

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)

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

list_versions — List Versions

parameter in type required
fn_id path integer yes

Returns: list of VersionOut

create_version — Create Version

parameter in type required
fn_id path integer yes

Request body: VersionCreate

Returns: VersionOut

me — Me

Returns: MeOut

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

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

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

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)

list_platform_prompts — List Platform Prompts

Returns: list of PromptBriefOut

create_platform_prompt — Create Platform Prompt

Request body: JSON object

Returns: PromptCreatedOut

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)

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

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)

update_space — Update Space

parameter in type required
space_id path string yes

Request body: SpaceUpdate

Returns: SpaceCreatedOut

restore_space — Restore Space

parameter in type required
space_id path string yes

Returns: JSON (shape documented in the handler)

get_version — Get Version

parameter in type required
v_id path integer yes

Returns: VersionOut

update_version — Update Version

parameter in type required
v_id path integer yes

Request body: VersionUpdate

Returns: VersionOut

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)

Per-function score library (checks/judges) and gate flags.

list_scores — List Scores

parameter in type required
fn_id path integer yes

Returns: list of ScoreOut

create_score — Create Score

parameter in type required
fn_id path integer yes

Request body: ScoreCreate

Returns: ScoreOut

delete_score — Delete Score

parameter in type required
score_id path integer yes

Returns: JSON (shape documented in the handler)

Measurement catalogue, plans, gate → plan → accept.

get_catalogue — Get Catalogue

The measurement catalogue as data — the browser groups these by dimension.

Returns: list of CatalogueEntryOut

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

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_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

rename_plan — Rename Plan

parameter in type required
plan_id path integer yes

Request body: JSON object

Returns: PlanCreatedOut

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

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

plan_scores — Plan Scores

parameter in type required
plan_id path integer yes

Returns: list of ScoreOut

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

run_gate — Run Gate

parameter in type required
v_id path integer yes

Returns: PlanOut

get_plan — Get Plan

parameter in type required
v_id path integer yes

Returns: PlanOut

run_plan — Run Plan

parameter in type required
v_id path integer yes

Request body: JSON object

Returns: PlanOut

Experiments over versions × models × repeats; findings.

delete_experiment — Delete Experiment

parameter in type required
exp_id path integer yes

Returns: JSON (shape documented in the handler)

get_experiment — Get Experiment

parameter in type required
exp_id path integer yes

Returns: ExperimentOut

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

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

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

list_findings — List Findings

parameter in type required
exp_id path integer yes

Returns: list of FindingOut

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

add_score_from_finding — Add Score From Finding

parameter in type required
finding_id path integer yes

Returns: ScoreOut

import_one_shot_agent — Import One Shot Agent

Request body: ImportRequest

Returns: ImportResultOut

list_experiments — List Experiments

parameter in type required
fn_id path integer yes

Returns: list of ExperimentOut

create_experiment — Create Experiment

parameter in type required
fn_id path integer yes

Request body: ExperimentCreate

Returns: ExperimentOut

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

list_tools — List Tools

Returns: list of ToolInfoOut

playground_run — Playground Run

parameter in type required
v_id path integer yes

Request body: PlaygroundRun

Returns: PlaygroundOut

Datasets, rows, snapshots, uploads.

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_dataset — Patch Dataset

parameter in type required
ds_id path integer yes

Request body: DatasetPatch

Returns: DatasetOut

list_rows — List Rows

parameter in type required
ds_id path integer yes

Returns: list of RowOut

add_row — Add Row

parameter in type required
ds_id path integer yes

Request body: RowIn

Returns: RowOut

snapshot_dataset — Snapshot Dataset

parameter in type required
ds_id path integer yes

Request body: JSON object

Returns: SnapshotOut

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

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

list_datasets — List Datasets

parameter in type required
fn_id path integer yes
include_archived query boolean no

Returns: list of DatasetOut

create_dataset — Create Dataset

parameter in type required
fn_id path integer yes

Request body: DatasetCreate

Returns: DatasetOut

delete_row — Delete Row

parameter in type required
row_id path integer yes

Returns: JSON (shape documented in the handler)

update_row — Update Row

parameter in type required
row_id path integer yes

Request body: RowUpdate

Returns: RowOut

Deploy/rollback/history + per-function invoke keys (fnk_*).

deploy — Deploy

parameter in type required
fn_id path integer yes

Request body: JSON object

Returns: DeploymentOut

current_deployment — Current Deployment

parameter in type required
fn_id path integer yes

Returns: DeploymentOut

deployment_history — Deployment History

parameter in type required
fn_id path integer yes

Returns: list of DeploymentOut

list_api_keys — List Api Keys

parameter in type required
fn_id path integer yes

Returns: list of InvokeKeyOut

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

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

revoke_api_key — Revoke Api Key

parameter in type required
key_id path integer yes

Returns: JSON (shape documented in the handler)

Trace explorer, function health, flag-to-dataset flywheel.

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

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_trace — Get Trace

parameter in type required
trace_id path string yes

Returns: TraceDetailOut

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_catalog — Get Catalog

Returns: list of PriceOut

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.

list_keys — List Keys

Returns: list of KeyOut

create_key — Create Key

Request body: KeyCreate

Returns: KeyOut

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_key — Delete Key

parameter in type required
key_id path integer yes

Returns: JSON (shape documented in the handler)

patch_key — Patch Key

parameter in type required
key_id path integer yes

Request body: KeyPatch

Returns: KeyOut

rotate_key — Rotate Key

parameter in type required
key_id path integer yes

Request body: KeyRotate

Returns: KeyOut

test_key — Test Key

parameter in type required
key_id path integer yes

Returns: KeyTestOut

Sign-in coordinates + personal access tokens (fnp_*).

auth_config — Auth Config

Returns: AuthConfigOut

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

list_tokens — List Tokens

The caller’s own tokens — hashes never leave the store.

Returns: list of TokenOut

mint_token — Mint Token

Mint a personal access token. The plaintext is in this response ONLY.

Request body: JSON object

Returns: TokenMintedOut

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.

run — Run

parameter in type required
fn_slug path string yes

Request body: JSON object

Returns: RunOut

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

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_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

revoke_elevation — Revoke Elevation

Returns: McpElevationOut

get_elevation — Get Elevation

Returns: McpElevationOut

grant_elevation — Grant Elevation

Returns: McpElevationOut

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

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

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

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

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

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

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)

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)

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

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

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

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_org — Get Org

Returns: OrgOut

update_org — Update Org

Request body: OrgUpdate

Returns: OrgOut

health — Health

Returns: JSON (shape documented in the handler)