Every route the server dispatches, in matching order, from the
declared route table (server/routes.rkt). auth
is how a route authenticates: public needs no
credential; bearer a session or API token;
provision the hosted-mode provisioning token;
superadmin and org-admin the two
multi-tenancy planes. The permission is the one the handler enforces on
its main path — owner-ok and resource grants may admit a caller the role
would not. A feature flag, when named, must be on for the team.
Path segments written :name are parameters;
*name takes the rest of the path.
| Method | Path | Auth | Permission | Feature | Description |
|---|---|---|---|---|---|
| GET | / |
public | — | — | The console (or the beta funnel when TELEMACHUS_HOME=beta). The HTML title is rewritten from instance branding. |
| GET | /index.html |
public | — | — | The console. |
| GET | /login |
public | — | — | The console, sign-in first — a URL that never depends on the beta landing. |
| GET | /activate |
public | — | — | The console, on the magic-link activation screen (hosted mode). |
| GET | /health |
public | — | — | Liveness: {ok, multitenant} and nothing else — version, KDF and TLS state are on GET /api/admin/status. |
| GET | /beta-sdk.js |
public | — | — | The browser SDK a Tier-B onboarding bundle loads (window.Telemachus.beta). |
| GET | /beta/bundle/:plugin/*path |
public | — | — | A file from a Tier-B onboarding plugin's PUBLIC landing bundle
(plugins/ |
| GET | /api/x/:plugin/bundle/*path |
bearer | — | — | A file from a loaded plugin's AUTHENTICATED bundle
(plugins/ |
| GET | /beta/template |
public | — | — | The Tier-C sandboxed HTML template, localized by the experience overlay. |
| GET | /api/config |
public | — | — | Public instance configuration: home mode, multi-tenancy flag, the localization policy (default locale, available locales, whether switching is enabled). The sign-in screen reads it before anyone has a token. |
| GET | /api/branding |
public | — | — | Title, tagline, logo and theme tokens. Public: the sign-in screen renders them. On a company's hostname, or for its signed-in user, the company's own (TEN-2d). |
| PUT | /api/branding |
bearer | instance:manage |
— | Set the instance title, tagline and theme. A theme token that is unknown, malformed, or below the WCAG contrast floor is a 400 naming it. |
| POST | /api/branding/logo |
bearer | instance:manage |
— | Upload the instance logo (replaces the mark and the wordmark). |
| GET | /api/i18n/catalog |
public | — | — | The console's strings for ?locale=, resolved through the fallback chain server-side. Public: the sign-in screen needs them. |
| PUT | /api/i18n |
bearer | instance:manage |
— | Set the instance default locale and whether users may switch. |
| GET | /api/l10n/coverage |
bearer | localization:read |
— | Per-locale coverage of the Localization Manager's catalog: total, approved, machine, missing, stale. |
| GET | /api/l10n/messages |
bearer | localization:read |
— | The messages of one locale, filterable by ?status=, ?ns=, ?q=, paged. |
| POST | /api/l10n/import |
bearer | localization:manage |
— | Pull the catalogs on disk into the Manager's tables. |
| POST | /api/l10n/export |
bearer | localization:manage |
— | Write a locale's APPROVED strings back to locales/ |
| POST | /api/l10n/draft |
bearer | localization:manage |
— | Queue AI drafts for a locale's missing strings, in batches of 20 scheduler jobs, quota-metered. |
| POST | /api/l10n/discard |
bearer | localization:manage |
— | Remove a locale's machine drafts (never a human's work). |
| PUT | /api/l10n/messages/:id |
bearer | localization:translate |
— | Submit a translation for one message. |
| POST | /api/l10n/review/:id |
bearer | localization:review |
— | Approve or send back a translation. A translator cannot approve their own. |
| GET | /api/beta/config |
public | — | — | The published onboarding experience's public slice (fields, copy, theme) in one language. |
| OPTIONS | /api/beta/signup |
public | — | — | CORS preflight for a sandboxed Tier-C template. |
| OPTIONS | /api/beta/config |
public | — | — | CORS preflight for a sandboxed Tier-C template. |
| OPTIONS | /api/beta/challenge |
public | — | — | CORS preflight for a sandboxed Tier-C template. |
| GET | /api/beta/experience |
bearer | — | — | The team's draft onboarding experience, including the judge prompt. |
| PUT | /api/beta/experience |
bearer | — | — | Save a draft of the onboarding experience. |
| POST | /api/beta/experience/publish |
bearer | — | — | Publish the saved draft; it then wins over the ENV-seeded default. |
| POST | /api/beta/assets |
bearer | — | — | Upload an onboarding asset (logo, hero image, font), base64 in JSON, capped at 2 MiB. |
| GET | /api/beta/assets |
bearer | — | — | List the team's onboarding assets. |
| GET | /api/beta/asset/:id |
public | — | — | Serve one onboarding asset. Public: the funnel and the branding logo load it without a token. |
| DELETE | /api/beta/asset/:id |
bearer | — | — | Delete an onboarding asset. |
| GET | /api/beta/challenge |
public | — | — | A proof-of-work challenge the funnel solves before signing up. |
| POST | /api/beta/signup |
public | — | — | Submit the onboarding form: validated against the published experience, anti-abuse gated, judged by the model when configured. |
| GET | /api/beta/prospects |
bearer | — | — | List the prospects the funnel captured, with signals and verdicts. |
| POST | /api/beta/prospects/:id/decide |
bearer | — | — | Accept or decline a prospect. |
| POST | /api/bootstrap |
public | — | — | First run only: create the operator, the first org and team. Returns the operator's token. Refuses once the instance has a user. |
| POST | /api/provision |
provision | — | — | Hosted mode: provision this instance with exactly one owner and a magic activation link. |
| POST | /api/activate |
public | — | — | Hosted mode: redeem an activation token and set the owner's password. |
| POST | /api/instance/suspend |
provision | — | — | Hosted mode: suspend the tenant (writes are refused with 402). |
| POST | /api/instance/resume |
provision | — | — | Hosted mode: resume a suspended tenant. |
| POST | /api/instance/quota |
provision | — | — | Hosted mode: set a quota limit for the tenant's team. |
| POST | /api/login |
public | — | — | Sign in with username and password (and a TOTP code when 2FA is enabled). Returns a bearer token. |
| POST | /api/2fa/enable |
bearer | — | — | Enable TOTP two-factor authentication for the caller; returns the secret and a set of single-use recovery codes, once. |
| POST | /api/2fa/recovery-codes |
bearer | — | — | Issue a fresh set of single-use recovery codes, invalidating any outstanding ones; returned once (issue #26). |
| GET | /api/2fa/recovery-codes |
bearer | — | — | How many of the caller's recovery codes are still unspent. Never the codes themselves. |
| DELETE | /api/2fa |
bearer | — | — | Turn the caller's own TOTP off, so the old seed and its recovery codes stop working and they must enrol again. |
| DELETE | /api/admin/users/:id/2fa |
bearer | instance:manage |
— | Revoke a user's TOTP seed (issue #19): a seed that may sit in a database dump cannot be rotated by using it, so an operator can force a re-enrolment. |
| POST | /api/password |
bearer | — | — | Change the caller's password. |
| GET | /api/whoami |
bearer | — | — | The caller: user, team, operator flag, org, org role, locale, permissions. |
| POST | /api/profile |
bearer | — | — | Update the caller's profile (display name, locale). |
| POST | /api/members |
bearer | members:manage |
— | Add a member to the caller's team with a role; returns the new member's first token. |
| GET | /api/members |
bearer | — | — | The team's members and roles. |
| GET | /api/admin/status |
bearer | instance:manage |
— | Instance status: version, KDF, TLS, multi-tenancy and secrets-at-rest state, and counts of users, teams, orgs, notes, tokens, audit events, tenants. |
| POST | /api/orgs |
superadmin | instance:manage |
— | Create a company: org, first team, owner. An explicit slug is a natural key (409 on re-run). |
| GET | /api/orgs |
superadmin | instance:manage |
— | List every org on the instance. |
| POST | /api/orgs/:ref/suspend |
superadmin | instance:manage |
— | Suspend a company (id or slug); every team in it becomes read-only. |
| POST | /api/orgs/:ref/resume |
superadmin | instance:manage |
— | Resume a suspended company. |
| POST | /api/orgs/:ref/quota |
superadmin | instance:manage |
— | Set an org-level quota; teams nest beneath it. |
| GET | /api/orgs/:ref |
superadmin | instance:manage |
— | One company, its teams and quotas. |
| PATCH | /api/orgs/:ref |
superadmin | instance:manage |
— | Rename a company, change its plan (re-applies the plan's caps), and/or set its hostname ({domain}, null to clear): the console on that host wears the company's branding before sign-in (TEN-2d). |
| POST | /api/admin/seed-tenants |
superadmin | instance:manage |
— | Seed Acme and Globex with known dev passwords — demo fixture only. |
| GET | /api/org |
org-admin | org:read |
— | The caller's own company. |
| GET | /api/org/teams |
org-admin | org:read |
— | The teams in the caller's company. |
| POST | /api/org/teams |
org-admin | org:manage |
— | Create a team in the caller's company. |
| POST | /api/org/members |
org-admin | org:manage |
— | Add a person to a team in the caller's company, optionally with an org role (org_admin, org_owner, org_reader). |
| PATCH | /api/org/members/:id |
org-admin | org:manage |
— | Set or clear a person's org role — the way an existing user becomes an org_reader (TEN-2h). Never your own. |
| GET | /api/org/audit |
org-admin | org:read |
— | The company's audit trail. |
| GET | /api/org/branding |
org-admin | org:read |
— | The company's own branding (TEN-2d) — or the instance's, with own:false, when it has set none. |
| PUT | /api/org/branding |
org-admin | org:manage |
— | Set the company's title, tagline, logo and theme: what the console wears on the company's hostname and for its signed-in users. Theme tokens are validated and contrast-gated exactly as the instance's are. |
| DELETE | /api/org/branding |
org-admin | org:manage |
— | Drop the company's branding; its users see the instance's again. |
| POST | /api/org/branding/logo |
org-admin | org:manage |
— | Upload the company's logo (base64) and make it the company's mark. |
| POST | /api/notes |
bearer | notes:write |
— | Create a note with a visibility. |
| GET | /api/notes |
bearer | notes:read |
— | List the notes the caller can read; ?scope=org spans every team in the company (an org_reader sees team-visible notes, never private ones). |
| POST | /api/documents |
bearer | files:write |
— | Create a text document (a repository object with content_type text/markdown). |
| GET | /api/documents |
bearer | files:read |
— | List text documents. |
| GET | /api/documents/:id |
bearer | files:read |
— | One text document with its body. |
| PUT | /api/documents/:id |
bearer | files:write |
— | Update a text document's title, body or visibility (a new version). |
| DELETE | /api/documents/:id |
bearer | files:delete |
— | Delete a text document. |
| POST | /api/notes/:id/share |
bearer | notes:manage |
— | Share a note with a user (owner, or notes:manage). The permission granted must be a notes:* one. |
| GET | /api/notes/:id |
bearer | notes:read |
— | One note. |
| PUT | /api/notes/:id |
bearer | notes:write |
— | Update a note. |
| DELETE | /api/notes/:id |
bearer | notes:delete |
— | Delete a note. |
| POST | /api/ai/echo |
bearer | chat:use |
— | A metered no-model echo, for exercising quotas. |
| POST | /api/ai/chat |
bearer | chat:use |
chat |
One model turn. prompt is a string or a list of content
parts; response_format ({type:
text|json_object|json_schema}) rides to the model and the reply is
refused if it does not honour it. Quota-admitted (ai.requests,
ai.tokens.total), governed by the team's ai.concurrency. Routing to a
named executor needs instance:manage. |
| POST | /api/ai/chat/stream |
bearer | chat:use |
chat |
The same turn as server-sent events, metered at the end. A
response_format is judged after the stream ends: the final event carries
parsed, or schema_error when the model did not
honour it. |
| POST | /api/agent |
bearer | chat:use |
agent |
Run the tool-using agent loop over the team's enabled tools; every tool call is RBAC-checked and metered. |
| POST | /api/translate/catalog |
bearer | chat:use |
translate |
Translate a whole locale catalog, placeholders preserved. |
| POST | /api/translate |
bearer | chat:use |
translate |
Translate text into a target language, applying the team glossary. |
| GET | /api/translate |
bearer | chat:use |
— | The team's translation history. |
| POST | /api/glossary |
bearer | settings:manage |
— | Add or update a glossary term for a target language. |
| GET | /api/glossary |
bearer | chat:use |
— | The team glossary. |
| GET | /api/ai/model |
bearer | — | — | Which model is configured (or that the simulated fallback is in use). |
| GET | /api/model-roles |
bearer | — | — | Which executor the team's bulk work goes to, per role (utility: knowledge-graph and field extraction, translation drafts), and the instance default. |
| PUT | /api/model-roles |
bearer | settings:manage |
— | Set a team's model roles: {roles: {utility: |
| GET | /api/executors |
bearer | — | — | The local executor, the env-configured push ones, and the API-created ones (push, or pull with a worker) with health. |
| POST | /api/executors |
bearer | instance:manage |
— | Create an executor: {name, mode: pull|push, model?, url?, key?, capabilities?, org_id?}. A pull executor's worker token is in this response and never again. |
| DELETE | /api/executors/:id |
bearer | instance:manage |
— | Retire an executor: its worker token is revoked and any job it holds returns to the queue. |
| POST | /api/org/executors |
org-admin | org:manage |
— | Create an executor bound to the caller's company (TEN-2e): offered only to its teams. |
| POST | /api/workers/claim |
bearer | jobs:execute |
— | A pull worker claims the next remote job it can run: {kinds, models, max_wait}; 204 when nothing. The job carries a lease and a claim_token, which every later write about it must carry back. |
| POST | /api/workers/jobs/:id/heartbeat |
bearer | jobs:execute |
— | Extend the lease on a job this worker holds: {claim_token}, the token the claim returned. A missing or stale token is a 409. |
| POST | /api/workers/jobs/:id/complete |
bearer | jobs:execute |
— | Post a result: {result, claim_token}. Accepted only from the current lease holder AND the attempt the token names (a lapsed attempt cannot land on the one that replaced it); validated by the kind. |
| POST | /api/workers/jobs/:id/fail |
bearer | jobs:execute |
— | Report a failure: {error, claim_token}. Accepted only from the current lease holder and that attempt. |
| GET | /api/usage |
bearer | — | — | The team's quota dimensions with used, limit and remaining. |
| POST | /api/quota |
bearer | instance:manage |
— | Set a quota limit for the caller's team (dimension, limit, window). |
| GET | /api/tools |
bearer | — | — | Every registered tool with its permission, source and per-team enabled state. |
| POST | /api/tokens |
bearer | settings:manage |
— | Issue an API token, optionally scoped, with a ttl in seconds (default 90 days; "never" for a long-lived machine token); the raw token is shown once. |
| GET | /api/tokens |
bearer | settings:manage |
— | The team's API tokens. |
| DELETE | /api/tokens/:id |
bearer | settings:manage |
— | Revoke an API token. |
| GET | /api/search |
bearer | — | search |
Search notes, repository objects (key, filename, extracted text) and knowledge-graph entities; every row filtered by can?; ?scope=org spans the company's teams for an org_reader. |
| GET | /api/audit |
bearer | settings:manage |
— | The team's recent audit events. |
| POST | /api/jobs |
bearer | chat:use |
— | Enqueue a scheduler job of a registered kind. |
| GET | /api/jobs |
bearer | — | — | The team's jobs, newest first. |
| POST | /api/jobs/:id/cancel |
bearer | — | — | Cancel a queued job (a running one finishes). |
| GET | /api/jobs/:id |
bearer | — | — | One job with its result or error. |
| GET | /api/repo |
bearer | files:read |
— | List repository objects by ?prefix=, paged; ?shared=1 lists only what the caller holds a live grant on and does not own; ?scope=org spans every team in the company (TEN-2h). |
| POST | /api/repo-obj/:id/share |
bearer | files:manage |
— | Share with a user or a team in the org: {principal_type, principal_id, capability: view|edit|manage, expires_at?}. {user_id} still means view. |
| POST | /api/repo-obj/:id/unshare |
bearer | files:manage |
— | Revoke every grant a principal holds on the object. |
| GET | /api/repo-obj/:id/grants |
bearer | files:manage |
— | One entry per principal: capability, permissions, granted_by, expiry, expired. |
| GET | /api/repo-obj/:id/derivations |
bearer | files:read |
— | Provenance: what the document was derived from, by which run and step. |
| GET | /api/repo-obj/:id/processing |
bearer | files:read |
— | "Processed by": derived documents, sources, and every run that touched the document. |
| GET | /api/share-targets |
bearer | files:read |
— | Who a document can be shared with: the team's people and the org's teams. |
| POST | /api/repo-obj/:id/visibility |
bearer | files:manage |
— | Set private, team or shared. |
| GET | /api/repo-obj/:id/content |
bearer | files:read |
— | Download the bytes (?version= for history). Attachment + nosniff unless the type is on the inline allowlist. |
| POST | /api/repo-obj/:id/presign |
bearer | files:read |
— | A time-boxed presigned S3 URL, signed with the caller's own newest S3 key. |
| GET | /api/repo-obj/:id |
bearer | files:read |
— | Object metadata with its versions. |
| DELETE | /api/repo-obj/:id |
bearer | files:delete |
— | Delete an object (blobs are released when no other version references them). |
| PUT | /api/repo/*key |
bearer | files:write |
— | Upload a raw body to a key (?filename=, ?visibility=); writing an existing key adds a version. Triggers evaluate here. |
| GET | /api/s3/credentials |
bearer | — | — | The caller's S3 access keys (secrets never shown again) and the endpoint/bucket to use them with. |
| POST | /api/s3/credentials |
bearer | — | — | Issue an S3 access key, optionally scoped (add workflows:run for a prefix that should fire triggers). |
| DELETE | /api/s3/credentials/:id |
bearer | — | — | Revoke an S3 access key; presigned links signed with it die with it. |
| POST | /api/workflows |
bearer | workflows:write |
workflows |
Publish a workflow spec (validated; unknown fields refused); re-publishing a slug bumps its version. |
| GET | /api/workflows |
bearer | workflows:read |
workflows |
The team's workflow definitions, plugin-contributed ones materialized on first look. |
| GET | /api/workflows/schema |
public | — | — | The workflow spec format: version, step kinds, binding references, predicates. |
| POST | /api/workflows/:slug/run |
bearer | workflows:run |
workflows |
Start a run with {input}; declared inputs are required and typed. Returns 202 with the run. |
| GET | /api/doc-triggers |
bearer | workflows:read |
workflows |
The team's upload triggers, with the team's remaining AI budget. |
| POST | /api/doc-triggers |
bearer | workflows:write |
workflows |
Create a trigger: workflow_slug, match_prefix, match_types, input, fire_on_derived. |
| GET | /api/doc-triggers/:id |
bearer | workflows:read |
— | One trigger with its fire history (which version, which run, or why not). |
| PATCH | /api/doc-triggers/:id |
bearer | workflows:write |
— | Change a trigger's fields; absent fields are left alone. |
| DELETE | /api/doc-triggers/:id |
bearer | workflows:write |
— | Delete a trigger and its history; the runs it started remain. |
| GET | /api/workflows/:slug |
bearer | workflows:read |
workflows |
One workflow definition with its spec. |
| GET | /api/runs |
bearer | workflows:read |
workflows |
The team's runs, newest first. |
| POST | /api/runs/:id/cancel |
bearer | workflows:run |
workflows |
Cancel a run; a step already executing finishes, the next never starts. |
| GET | /api/runs/:id |
bearer | workflows:read |
workflows |
One run with its steps, inputs, outputs and errors. |
| GET | /api/kg/entities |
bearer | files:read |
— | Find entities by ?q= and ?type=; only those with a mention the caller can read. |
| GET | /api/kg/entities/:id |
bearer | files:read |
— | One entity with its relations and mentions, each mention filtered by the source document's visibility. |
| POST | /api/kg/extract |
bearer | workflows:run |
workflows |
Queue the index-knowledge workflow over the team's unextracted documents. |
| POST | /api/admin/seed |
bearer | settings:manage |
— | Seed sample notes and documents into the caller's team. |
| GET | /api/metrics |
bearer | instance:manage |
— | Operational metrics. |
| GET | /api/features |
bearer | — | — | The team's feature flags. |
| POST | /api/features/:name |
bearer | settings:manage |
— | Enable or disable a feature for the team. |
| GET | /api/plugins |
bearer | — | — | The loaded plugins with their tools and workflows. |
| GET | /api/mcp |
bearer | — | — | Connected MCP servers. |
| GET | /api/oop |
bearer | — | — | Connected sandboxed (out-of-process) plugins and the capabilities they may ask for. |
| POST | /api/tools/:name |
bearer | settings:manage |
— | Enable or disable a tool for the team. |
Routes contributed by loaded plugins, mounted under
/api/x/<plugin>/. Every one requires a bearer token;
the permission, when named, is checked before the plugin's handler runs.
Matched after the core routes above.
| Method | Path | Plugin | Permission | Description |
|---|---|---|---|---|
| GET | /api/x/example-tools/word-count |
example-tools |
chat:use |
Count the words in ?text=. |
| POST | /api/x/example-tools/word-count |
example-tools |
chat:use |
Count the words in {text}. |
| GET | /api/x/integrator-demo/eta |
integrator-demo |
chat:use |
Estimated days for ?lane=. |
| POST | /api/x/integrator-demo/eta |
integrator-demo |
chat:use |
Estimated days for {lane}. |