Skip to content
OpenSmartRoute

Platform guide

Authentication, POST /api/v1/route, the OpenAI-compatible endpoint, feedback, plans, tenants, dashboard and errors.

docs/PLATFORM.md

The hosted platform runs OpenSmartRoute as a service. A workspace has an API key and a plan; each request to the API is routed to a target (model, agent, skill, persona or tool) and answered with the decision, its trace and, on request, the target's output. An OpenAI-compatible endpoint accepts existing chat-completion clients.

This page covers calling the platform: authentication, routing, execution, the OpenAI-compatible endpoint, feedback, plans, organizations, tenants, governance (workspace policy and budgets), observability (probes, metrics, per-request traces and the event stream), caching, the dashboard and the MCP server. The marketplace - shared agents, skills, personas, prompts and stack templates - has its own page. To run the router on your own infrastructure see Deploy with Docker and Helm; to use it as a Python library see the user guide and the SDK guide. Every endpoint below is listed in the REST API reference (/docs/api) and in /openapi.json.

1. Authentication#

  1. Sign up at /platform/signup with GitHub, Google or an email address - with a password if you want to sign in by email later (OSR_PLATFORM_PASSWORD_LOGIN, on by default). Signup creates a personal workspace on the free plan and issues an API key.
  2. The key is shown once. Store it in a secret manager. Keys are created and revoked in the dashboard (/platform/dashboard/keys) or with POST /api/v1/keys and DELETE /api/v1/keys/{key_id}.
  3. Send the key with every metered request in either header:
Authorization: Bearer osr_...
X-API-Key: osr_...

Public endpoints (/api/v1/info, /api/v1/models, /api/v1/llms, /api/v1/rankings, /api/v1/catalogue, /api/v1/estimate, /api/v1/stats/public, /api/v1/registry) need no key.

Email and password#

POST /api/v1/auth/password/login {email, password} returns the same browser session as a single sign-on callback (session_token, user, account, plan); the dashboard stores it and sends it as a bearer token. Passwords are at least 8 characters (no composition rules) and are stored as salted scrypt hashes. A signed-in user sets or changes theirs with POST /api/v1/auth/password {password, current_password} - the current one is required once set and every other browser is signed out - and removes it with DELETE /api/v1/auth/password as long as a provider identity remains. Sign-in attempts are limited per address and per email; has_password on the user object says which users have one. /platform/dashboard/account has the form.

Account lifecycle: confirmation, recovery, deletion#

Anyone can create an account with any email address. What happens around it:

StepHowEndpoints
Email confirmationSignup sends a link to /platform/verify-email?token=... (valid three days). Signing in through a provider that vouches for the address, accepting an invitation sent to it or completing a password reset also confirms it. email_verified on the user object; /platform/dashboard/account shows a banner with Send again until it is done.POST /api/v1/auth/verify {token}, POST /api/v1/auth/verify/send (signed in, once a minute)
Forgot password/platform/forgot-password emails a single-use link to /platform/reset-password?token=... (one hour). The answer is always 202 so it never reveals whether an address has an account; limited per address and per email. Resetting revokes every session, confirms the address, signs the person in and sends a notice.POST /api/v1/auth/password/forgot {email}, POST /api/v1/auth/password/reset {token, password}
InvitationsPOST /api/v1/workspace/invites emails the invitee a link to /platform/invite/<token> (fourteen days) and returns the same link once to the inviter.see Organizations
Account historyThe person's own sign-ins, password and email changes and memberships, newest first.GET /api/v1/me/audit
Delete accountDelete my account on /platform/dashboard/account: type the email (and the password when one is set). Sessions, linked identities and memberships go, workspaces only this user belonged to are disabled and their keys revoked; an organization with other members needs another owner first. A notice is emailed.POST /api/v1/me/delete {confirm, password}
Getting startedThe checklist of a workspace: e-mail confirmed, API key created, first request routed, first outcome reported, a model provider connected and - for organizations - a teammate invited; each step carries the dashboard link and the API call that completes it, next is the first open one and complete the share done. The dashboard overview shows it until every step is done.GET /api/v1/onboarding

Signup, confirmation, password recovery, invitation links and this checklist form the onboarding domain, which a deployment can run as its own service (see Services).

Every one of these events - plus sign-ins, failed sign-ins, role changes, removals and operator actions

  • is written to the account audit log. Workspace admins read their workspace's entries with GET /api/v1/workspace/audit; operators see everything at /platform/admin/audit (GET /api/v1/admin/audit-log) and per user on /platform/admin/users/<id>, where they can also issue a reset link, re-send or force the email confirmation, disable, sign out everywhere or delete the user.

Email is delivered through the SMTP server in OSR_PLATFORM_SMTP_URL. Without one, nothing is lost: every message is kept in the outbox and operators pick the links up at /platform/admin/mail (GET /api/v1/admin/mail); mail in GET /api/v1/info tells the web app which case applies.

Command line sign-in#

Install the CLI (curl -LsSf https://opensmartroute.ai/install.sh | sh, or irm https://opensmartroute.ai/install.ps1 | iex on Windows) and run osr login. The CLI shows an eight-character code and opens /platform/cli/authorize; sign in there, check that the code and computer name match, choose the key name and approve. The platform mints a new workspace API key for that machine and the CLI stores it in ~/.config/opensmartroute/credentials.json. This is the OAuth 2.0 device authorization grant (RFC 8628) and works the same for the community and enterprise editions:

StepEndpointNotes
CLI asks for a codePOST /api/v1/auth/device/code {client_name}anonymous, rate limited per IP; returns device_code, user_code, verification_uri[_complete], expires_in (900 s), interval (5 s)
Browser looks it upGET /api/v1/auth/device/{user_code}signed in; shows client name, status and expiry
Browser decidesPOST /api/v1/auth/device/approve / deny {user_code, name}approving needs the admin role and a free key slot in the plan
CLI pollsPOST /api/v1/auth/device/token {device_code}authorization_pending, slow_down, access_denied, expired_token as RFC 8628 errors; success returns access_token (the osr_live_ key), account, plan, edition

Only approve codes from a terminal you started yourself. Keys minted this way appear on /platform/dashboard/keys with the chosen name and can be revoked like any other. Pasting a key works too (osr login --token osr_live_..., or --with-token from stdin in CI); osr whoami shows the workspace, plan and edition behind the stored credential, osr token create|list|revoke manage keys and osr mcp --remote bridges an IDE to this workspace's MCP server without copying the key around.

2. Route a request#

POST /api/v1/route returns a decision: the chosen target, the confidence, ranked alternatives and the signals extracted from the request. The target is not called unless execute is set.

curl -s "$OSR_URL/api/v1/route" \
  -H "Authorization: Bearer $OSR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Prove that sqrt(2) is irrational.", "top_k": 3}'
{
  "request_id": "7f3c...",
  "target": {"id": "llm-frontier", "kind": "llm", "name": "Frontier model"},
  "confidence": 0.81,
  "alternatives": [{"id": "llm-mid", "kind": "llm", "utility": 0.62}],
  "signals": {"complexity": 0.74, "domains": ["math"], "reasoning_need": 0.9, "contains_pii": false, "...": "..."},
  "ranked": [{"id": "llm-frontier", "utility": 0.79, "quality_estimate": 0.91, "breakdown": {"...": "..."}}],
  "policy_rejections": {},
  "explanation": "llm-frontier: high reasoning need, math domain, quality weight dominates ...",
  "executable": true,
  "elapsed_ms": 1.9
}

Request fields:

FieldTypeMeaning
textstring, requiredThe user request.
historylist of {role, content}Prior turns; the router reads them for intent, task type and session affinity.
contextobjectFree-form facts (app, session_id, user_locale, ...). Keys starting with _ are dropped.
objectiveobjectRelative weights: quality (default 1.0), cost (0.15), latency (0.05), plus quality_floor, energy, carbon. cost_weight is accepted as a synonym of cost.
constraintsobjectHard constraints, never traded off: see below.
profileobjectOpaque user/tenant features (locale, expertise, preferences). Hashed into the learners; never logged raw.
kindslistRestrict to target kinds: llm, agent, skill, persona, tool, human.
top_kint, default 3How many alternatives to return.
planboolBuild a persona → skill → model plan instead of choosing a single target (pro plan and above).
executeboolRoute and run the chosen target or plan; the output is returned in result (pro and above).

Constraints (constraints object):

KeyEffect
max_cost_per_1kDrop targets whose price per 1k tokens is above this.
max_latency_msDrop targets slower than this.
preferred_max_latency_msSoft target: targets whose observed p90 latency is above it are penalised, not dropped.
regionOnly targets served from this region.
data_boundaryOnly targets at least this strict (public < private < on_prem).
contains_piiForce the PII handling on or off (null lets the sensitivity signal decide).
allow_targets / deny_targetsExplicit allow / deny lists of target ids.
allowed_kindsSame as kinds.
require_toolsOnly targets that can call tools.

Quote before routing#

POST /api/v1/estimate takes the same body and returns the price of each candidate without calling anything: targets[] with cost_usd, input_tokens, output_tokens, vendor and model, plus the router's recommended pick and the cheapest, best_quality and fastest candidates. output_tokens overrides the assumed reply length; monthly_requests adds a monthly projection (recommended_usd, cheapest_usd, dearest_usd, savings_usd). Without a key it is rate limited per client and ignores tenant rules; with a key it is metered as estimate and applies your plan and tenants. GET /api/v1/models/recommended returns the router's picks per use case from live quotes. Details, the SDK form and the /estimate page: MCP.md.

3. Execute the chosen target#

With "execute": true the platform runs the chosen target (or plan) with the configured providers and returns the output, the steps that ran and the metered cost:

curl -s "$OSR_URL/api/v1/route" -H "Authorization: Bearer $OSR_API_KEY" -H "Content-Type: application/json" \
  -d '{"text": "Summarise this contract clause for a non-lawyer: ...", "execute": true, "objective": {"cost": 0.4}}'

The response adds result with text, target_id, steps[] (role, target_id, ok, latency_ms), tokens and cost_usd.

Model providers#

A target executes when it is mapped to a model on an OpenAI-compatible endpoint - OpenAI, Azure OpenAI, OpenRouter, Mistral, Groq, Together, Fireworks, DeepSeek, a local Ollama, vLLM or LiteLLM, anything that speaks /v1/chat/completions. Two sources define the mapping and are merged (the console wins on the same provider name or target):

  • the file the deployment mounts, OSR_PLATFORM_PROVIDERS (YAML/JSON path or inline JSON) with providers.<name> (base_url, api_key or api_key_env, optional api_key_header, extra_headers, timeout_s, max_retries) and models.<target_id> (provider, model, optional system_prompt and request defaults). On first start it is imported into the console once, so it can be edited there.
  • the operator console, /platform/admin/providers: add an endpoint from a preset, store the key inline or name the environment variable that holds it, Check it (GET <base_url>/models with its credentials records reachability, latency and the upstream model ids, which the mapping form then offers), map targets to upstream models, disable or delete. Every write re-binds the handlers in the API at once, and every other API replica re-reads the store within OSR_PLATFORM_PROVIDERS_RELOAD_S (30 s) - no restart, no redeploy.

GET /api/v1/providers is the public, OpenRouter-style view of what a deployment executes on, rendered by the site at /providers: one row per executable target with provider, model, the recognised reference model, input_usd_per_1m / output_usd_per_1m (from the reference catalogue when the upstream model is known, else the target's declared cost), context window, tool and reasoning support, circuit-breaker health and seven-day traffic; plus each provider's host, kind, health and model count. Credentials never appear: the console shows only whether a key is set and its last four characters (api_key_set, api_key_hint). Keys stored through the console live in the platform database next to the organization SSO secrets; prefer api_key_env when the host injects secrets. Endpoints: GET|POST /api/v1/admin/providers, GET|PATCH|DELETE /api/v1/admin/providers/{id}, POST /api/v1/admin/providers/{id}/check, PUT|DELETE /api/v1/admin/providers/models/{target_id}. Targets without a mapping still route (decisions, plans, traces, quotes) and report executable: false.

Response headers#

HeaderOnMeaning
X-Request-Idevery responseThe id to quote in feedback and support requests; echoed back when you send one.
Server-Timingevery responseapp;dur=<ms> - time spent inside the platform.
X-OSR-Target/v1/chat/completionsThe target id that answered.
X-OSR-Confidence/v1/chat/completionsThe router's confidence in that choice.
Retry-After429Seconds until the per-minute window, the daily quota or the budget period resets.
X-Quota-Limit429 (daily)Your plan's requests per day.
X-Budget-Limit, X-Budget-Used, X-Budget-Period429 (budget)The exhausted workspace or tenant budget in USD and its period (daily / monthly).
X-Upgrade: true403The feature exists but is not in your plan.
ETag, Cache-Controlpublic catalogue readsConditional requests: send If-None-Match and receive 304 when nothing changed.

4. OpenAI-compatible endpoint#

Point an OpenAI SDK at $OSR_URL/v1 and set model to auto. The router chooses the target per request, runs it and returns a standard chat.completion whose model field is the target that answered. Decision metadata is added in an opensmartroute object that other clients ignore.

from openai import OpenAI

client = OpenAI(base_url=f"{OSR_URL}/v1", api_key=OSR_API_KEY)
reply = client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Write a haiku about routing."}],
)
print(reply.model)                 # e.g. "llm-small" - the target the router chose
print(reply.choices[0].message.content)
"opensmartroute": {
  "request_id": "7f3c...", "target": "llm-small", "kind": "llm", "confidence": 0.88,
  "plan": [], "alternatives": ["llm-mid"], "cost_usd": 0.00012, "latency_ms": 412.5,
  "fallback_from": []
}

Options outside the OpenAI schema go in the request body and are ignored by other providers:

FieldMeaning
modelauto (default) lets the router choose; a target id pins that target.
modelsCandidate list: the router chooses among them and falls back down the list when a target fails.
osr.objective / osr.constraintsSame as on /api/v1/route.
osr.tenantTenant slug (or the X-OSR-Tenant header); applies the tenant's constraints (enterprise).
osr.plantrue/false to force or suppress plan composition; omitted = whatever the plan tier allows.
osr.fallbacksfalse disables the automatic retry on the next best target (default true).
streamtrue streams server-sent events in the OpenAI format.

GET /v1/models lists auto and every executable target.

5. Feedback#

POST /api/v1/feedback reports how a routed answer turned out. The learners (Bradley-Terry, IRT, LinUCB) update on the workspace's own traffic. Available on the pro plan and above.

curl -s "$OSR_URL/api/v1/feedback" -H "Authorization: Bearer $OSR_API_KEY" -H "Content-Type: application/json" \
  -d '{"request_id": "7f3c...", "target_id": "llm-frontier", "success": true, "quality": 0.9, "latency_ms": 2100}'

Fields: request_id, target_id, success (required); quality (0..1), cost_usd, latency_ms, domains, complexity, preferred_over (the id of a target this one beat in a comparison).

Every report is kept against its request: GET /api/v1/activity summarises it per row (outcome), GET /api/v1/trace/{request_id} lists the reports (outcomes) next to the spans, and in the enterprise edition the audit chain records an outcome entry after the decision entry (section 10).

6. Plans and quotas#

PlanRequests / dayRequests / minKeysFeatures
free500302route + trace, OpenAI-compatible proxy
pro20,00030010+ plans (plan/execute), feedback
enterprise500,0003,000100+ tenants, /stats, hash-chained /audit, organization SSO, 50 tenants, 500 seats

GET /api/v1/info (plans) returns the values for the deployment you are calling; GET /api/v1/me returns your plan. Upgrades are made in the dashboard (/platform/dashboard/billing).

  • 429 rate limit exceeded - per-minute window; wait Retry-After seconds.
  • 429 daily quota exhausted - the day's budget; X-Quota-Limit shows the ceiling.
  • 403 with X-Upgrade: true - the feature is not in your plan.
  • 404 ... requires the enterprise edition - the deployment runs the community image, which does not expose tenants, statistics or audit regardless of plan.

7. Organizations#

Signup creates a personal workspace. An organization is a second kind of workspace with its own members, roles, API keys, tenants, plan and usage; one user can belong to several and switches between them in the dashboard (/platform/dashboard/workspace, POST /api/v1/workspaces/{account_id}/switch). Creating one (POST /api/v1/workspaces with name and an optional slug) makes you its owner and issues its first API key.

Roles are member < admin < owner:

ActionRole
Route, read usage, savings and activitymember
Create and revoke API keys, change tenants, invite and remove members, rename the workspaceadmin
Grant or remove owner, configure organization SSO, delete the connectionowner

API keys belong to the workspace and act with its full rights; the role model applies to signed-in browser sessions. Members are invited by email (POST /api/v1/workspace/invites): the invitee gets a message with the link when the deployment has a mail server, and the platform returns the same link once to the inviter so it can be shared directly. Seats and tenants are limited per plan (max_members, max_tenants in GET /api/v1/info). An organization always keeps at least one owner; owners transfer ownership before leaving.

Organization SSO (enterprise) lets people sign in with the company identity provider: PUT /api/v1/workspace/sso with kind (github, google, microsoft, gitlab or oidc with an issuer), client_id, client_secret, the allowed email domains and the default_role given to anyone who signs in from one of those domains. The workspace needs a slug first; the sign-in page shows the provider as org-<slug>.

8. Tenants#

Tenants (enterprise) apply a fixed set of hard constraints per customer, team or region without the caller repeating them. Create a tenant, then send its slug in X-OSR-Tenant (or osr.tenant).

curl -s -X PUT "$OSR_URL/api/v1/tenants" -H "Authorization: Bearer $OSR_API_KEY" -H "Content-Type: application/json" \
  -d '{"slug": "eu-health", "config": {"region": "eu", "data_boundary": "private", "max_cost_per_1k": 0.01, "deny_targets": ["llm-frontier"]}}'

curl -s "$OSR_URL/api/v1/route" -H "Authorization: Bearer $OSR_API_KEY" -H "X-OSR-Tenant: eu-health" \
  -H "Content-Type: application/json" -d '{"text": "Anonymise this discharge summary ..."}'

Tenant config keys: region, data_boundary, max_cost_per_1k, max_latency_ms, preferred_max_latency_ms, deny_targets, allow_targets, contains_pii, daily_budget_usd, monthly_budget_usd. Explicit request constraints win over the tenant defaults; the workspace policy (next section) applies on top of both. Executed spend is attributed to the tenant, so GET /api/v1/governance shows spend and budgets per tenant.

9. Governance: workspace policy, budgets and controls#

A workspace policy applies to every request of the workspace, whichever key or tenant sent it. It takes the same keys as a tenant plus budgets, and needs the admin role:

curl -s -X PUT "$OSR_URL/api/v1/policy" -H "Authorization: Bearer $OSR_API_KEY" -H "Content-Type: application/json" \
  -d '{"config": {"data_boundary": "private", "deny_targets": ["llm-frontier"], "monthly_budget_usd": 250}}'

GET /api/v1/policy returns the policy, the accepted keys and the current budget lines; DELETE removes it. Constraints merge deterministically: a boundary or region fills in when the request has none, cost and latency caps take the stricter value, deny lists are merged and allow lists intersected. Unknown keys, unknown target ids and an id present in both allow_targets and deny_targets are rejected with 400.

Budgets count executed spend (execute: true, /v1/chat/completions, MCP ask) - quotes and route-only calls are free. daily_budget_usd resets at midnight UTC, monthly_budget_usd on the first of the month. Once a workspace or tenant budget is exhausted, routed calls return 429 ... budget ... exhausted with Retry-After and the X-Budget-* headers until the period rolls over or the limit is raised.

GET /api/v1/governance is the compliance view of the workspace in one payload:

BlockContents
controlsWhat the deployment enforces: input_guard, pii_redaction, steering_strip, metrics, tracing, audit, health_breakers, decision_cache, learning (persisted or in-process).
auditWhether the hash-chained audit trail is on, its length and the result of the last verification.
retention_daysHow long usage rows are kept before the background sweep purges them.
policy, policy_keys, budgets, spendThe workspace policy, its accepted keys, budget lines with used and remaining amounts, spend today and this month.
quotaRequests today against the plan's daily and per-minute limits.
tenantsEach tenant with its monthly spend and budget lines.
catalogueTargets per data_boundary, targets that refuse PII, executable targets, providers, and which targets remain reachable under the policy.

The dashboard renders the same data at /platform/dashboard/governance, with a policy editor for admins.

10. Observability and caching#

The platform observes itself: every span, event, counter and alert below is produced, stored and served by the platform - there is no external metrics system, tracing backend or alerting tool to run. Every deployment exposes liveness and readiness probes and the counters without a key:

EndpointMeaning
GET /healthzProcess is up: edition, version, target count, uptime.
GET /readyz200 when the database, catalogue, router and (enterprise) audit file all answer; 503 with the failing check otherwise. Also reports database_dialect (sqlite or postgres), redis and events (the event bus) - those two are informational and never fail readiness.
GET /metricsThe counters in text exposition format: osr_platform_http_requests_total{method,route,status}, osr_platform_http_request_duration_ms_*, osr_platform_http_in_flight, plus the router's osr_route_decisions_total, osr_route_latency_ms_*, osr_outcome_* and osr_decision_cache_*.

Responses carry X-Request-Id (send your own to correlate with your logs) and Server-Timing; the API writes one JSON access-log line per request (osr.platform.access) with the route template, status, latency and account - never the prompt. GET /api/v1/stats (enterprise) adds http, controls, cache and observability snapshots; /platform/dashboard/health shows them live.

Time series and alerts#

EndpointReturns
GET /api/v1/telemetry/series?window=24hThe workspace's metered traffic bucketed over 1h (per minute), 6h (5 min), 24h (15 min), 7d (3 h) or 30d (per day): requests, failures, error_rate, p50 / p95 latency, cost and tokens per bucket, totals per target and per endpoint, and the window total. Computed from the platform's own usage records, so it agrees with the activity log to the row. Plans with stats also get http: the deployment's requests, 4xx, 5xx and p50 / p95 / p99 latency per bucket from the per-minute counters the process keeps for 24 hours.
GET /api/v1/alertsActive conditions, most severe first, each with the dashboard page that shows or fixes it. Workspace rules on every plan: a daily or monthly budget of the workspace or a tenant at 80 % (warning) or exhausted (critical), and 20 % or more of the last 15 minutes' metered requests failing (10+ requests). Deployment rules on plans with stats: a failing readiness check, 5 % or more 5xx over the last five minutes (20+ requests), p95 above 2 000 ms, an open or half-open circuit breaker per target, a routing-drift alarm in the last hour, a failed autopilot cycle, a routing SLM last fitted more than 30 days ago, and tracing being off.

The overview shows the last 24 hours (traffic, latency) and the alerts; /platform/dashboard/health adds the deployment series and the same alerts, refreshed with the page.

Notifications: alert delivery#

Alerts are evaluated by the API itself - no external scheduler. Every OSR_PLATFORM_ALERTS_INTERVAL_S seconds (default 60; 0 turns the loop off and leaves Evaluate now to operators) one replica - the one holding the osr:alerts:leader lease in Redis, or the only one without Redis - re-evaluates the workspace rules for every workspace that was active in the last 15 minutes or has a channel or an open alert, plus the deployment rules. Each condition becomes a notification episode: opened when the rule starts firing, refreshed while it stays on (an escalation from warning to critical is delivered again), resolved when it clears. Episodes land in an inbox with unread counts per severity, are published to the alerts Kafka topic in cluster mode, and are delivered to the workspace's channels:

KindTargetDelivery
emailAn addressThrough the platform mailer (OSR_PLATFORM_SMTP_URL, otherwise the outbox at /platform/admin/mail).
webhookAn https:// URLPOST of {event, alert, workspace, url} with X-OSR-Event (alert.firing, alert.escalated, alert.resolved, alert.test), X-OSR-Delivery and, when the channel has a secret, X-OSR-Signature: t=<unix>,v1=<hex> = HMAC-SHA256 of <t>.<body>; verify like Stripe signatures.
slackAn incoming-webhook URLBlock Kit message with severity, rule, value against threshold and a link to the page that fixes it.
teamsAn incoming-webhook URLMessageCard with the same facts.

A channel filters by minimum severity (info, warning, critical) and optionally by rule names, can be disabled, records its last delivery and last error, and has Send test. Failed webhook deliveries are retried on the following evaluation rounds up to three attempts. Up to 20 channels per workspace; creating or changing them takes the admin role. Operators have the same inbox and channels for the deployment scope (readiness, 5xx rate, p95, circuit breakers, drift, autopilot, SLM age, tracing) at /platform/admin/notifications.

EndpointPurpose
GET /api/v1/notifications?state=firing&unread=true&limit=100The workspace inbox (items) and unread counts per severity (unread).
GET /api/v1/notifications/unreadUnread counts only - the dashboard bell polls this.
POST /api/v1/notifications/read{"ids": [...]} marks those read, {"ids": null} marks everything read.
`GETPOST /api/v1/notifications/channels`
`PATCHDELETE /api/v1/notifications/channels/{id}`
POST /api/v1/notifications/channels/{id}/testDeliver a test notification and return ok, status, error.
GET /api/v1/notifications/deliveriesThe last delivery attempts (channel, event, attempt, status, error).
`GETPOST /api/v1/admin/notificationsand.../channels, .../deliveries`

Shared state and replicas#

A single API replica keeps everything on its data volume (SQLite, learner state, audit chain). A deployment that runs several replicas moves the shared parts to services that are themselves containers - no managed cloud database, cache or broker is required, and the same images run on a laptop, a Kubernetes cluster or Azure Container Apps:

StateServiceSetting
Accounts, users, keys, tenants, policies, usage, feedback, marketplacePostgreSQLOSR_PLATFORM_DATABASE_URL
Rate-limit windows, learner state (enterprise)RedisOSR_PLATFORM_REDIS_URL
usage, feedback and admin events as JSON topics osr.<name>Kafka (any Kafka-protocol broker)OSR_PLATFORM_KAFKA_BOOTSTRAP, OSR_PLATFORM_KAFKA_TOPIC_PREFIX

GET /api/v1/info -> storage says which mode a deployment runs ({database, clustered, redis, events}) and the operator console shows it on /platform/admin. Event payloads carry the same fields as the activity log and never the prompt text. platform/docker-compose.yml in the repository starts the complete stack; the Azure template provisions the three services as container apps with internal TCP ingress next to the API and web app.

In that stack the routing SLM is its own service too: the API publishes each routed prompt to the training topic (opt-in, OSR_PLATFORM_TRAINING_EVENTS), the SLM service pairs prompts with the outcomes reported through POST /api/v1/feedback, trains a challenger on a schedule, keeps it only when it beats the champion on a held-out split, and writes the promotion to the shared volume from which every API replica reloads it. Operators see the model in service, the evidence ingested, every cycle and a prompt probe on /platform/admin/slm (GET /api/v1/admin/slm, /slm/reports, POST /slm/cycle, /slm/predict).

Services#

The API is one image that can run as one process or as several: the documentation JSON (docs), the public model catalogue and rankings (rankings), the marketplace (marketplace), provider management (providers), signup and account links (onboarding), identity and workspaces (accounts: sign-in, sessions, organizations, keys, tenants, policy, governance, usage), Stripe billing (billing), the operator console API (admin), the MCP endpoint (mcp), the OpenAI-compatible proxy (openai), the routing core with its trace, event and learning reads (routing) and the routing SLM (slm) each have their own entry point and port. /api/v1/info, /api/v1/status and /api/v1/estimate always stay with the API. The API remains the single public origin and forwards a domain's paths to its service when OSR_PLATFORM_<NAME>_URL is set (streamed completions are relayed as they arrive); the answer carries X-OSR-Service naming the process that served it. GET /api/v1/info -> services lists the topology; operators see live health per service on /platform/admin/services (GET /api/v1/admin/services). The Compose stack and the Azure template deploy every service as its own container next to the gateway.

Traces and events#

The router records every request as a tree of spans (request, route, plan, execute) with events inside them (route.signals, route.policy, route.rank, route.fallback, guard.*, execute.step, learn.outcome, cache.hit, ...). The newest ones stay in an in-memory buffer (1000 by default, OSR_OBSERVABILITY_MEMORY_EVENTS) and every one of them is also written to the platform database by the platform's own telemetry store (OSR_PLATFORM_TELEMETRY_STORE, on by default) - off the request path, from a background writer, so tracing never slows routing. Events and traces therefore survive restarts, can be read by time range and are purged after OSR_PLATFORM_TELEMETRY_RETENTION_DAYS (14). They are readable per workspace:

EndpointReturns
GET /api/v1/trace/{request_id}The activity row plus every span and event of one request, in time order, with trace_id, spans and the root duration_ms; outcomes (the feedback reported for the request) and audit (its hash-chained records, plans with audit). source says whether the rows came from the buffer or the store. 404 when the request is not the workspace's; events: [] once the request is older than the telemetry retention.
GET /api/v1/events?name=route.*&kind=&level=&request_id=&since=&until=&limit=200Events of the workspace's requests (oldest first, limit up to 2000; name may end with *; since / until are epoch seconds, default the last 6 hours). Deployment-wide events without a request id (breaker transitions, autopilot) are included on plans with stats. source, persisted, retention_start and retention_days describe the store. 404 when tracing is off entirely.
GET /api/v1/statusPublic readiness: the /readyz checks plus edition, versions, uptime, controls and tracing state; 503 while a check fails. Cache-Control: no-store.

POST /api/v1/route responses include trace_id and the X-OSR-Trace-Id header when the request was traced, and GET /api/v1/activity marks rows whose spans are kept (in memory or in the store) with traced: true and summarises the feedback reported for each request in outcome (POST /api/v1/feedback is stored per request, so the loop from decision to result is visible per row). Attributes hold ids, numbers and short labels; the request text is represented only by its digest and length. The dashboard shows the history at /platform/dashboard/events (window of 1 hour to 7 days, search by request or trace id), opens a trace from any activity row (with a How it was routed summary of signals, policy, ranking and decision, the outcome and the audit records) and adds a Trace tab to the playground.

Public catalogue reads (/api/v1/info, /api/v1/models*, /api/v1/rankings, /api/v1/llms*, /api/v1/catalogue, /api/v1/stats/public) return an ETag and Cache-Control: public, max-age=60, stale-while-revalidate=60; send If-None-Match to get 304 when nothing changed. Operators tune this and the decision cache with:

SettingDefaultEffect
OSR_PLATFORM_METRICS_PUBLICtruefalse restricts /metrics to requests carrying X-Admin-Token.
OSR_PLATFORM_HTTP_CACHE_MAX_AGE_S60Cache-Control lifetime of public reads; 0 sends no-store.
OSR_PLATFORM_ROUTE_CACHE_TTL_S0Reuse a routing decision for identical requests within this window (0 = off).
OSR_PLATFORM_RETENTION_DAYS365Usage rows older than this are purged every six hours; 0 keeps everything.
OSR_PLATFORM_TELEMETRY_STOREtruePersist every span and event to the platform database (the events page, traces and since / until queries outlive the in-memory buffer).
OSR_PLATFORM_TELEMETRY_RETENTION_DAYS14Persisted spans and events older than this are purged with the same sweep; 0 keeps them.

Learning: the routing SLM and the autopilot#

Every deployment learns from POST /api/v1/feedback: the bandit, IRT, Bradley-Terry, LinUCB and Markov strategies update on each outcome (persisted under the data volume in the enterprise edition). A deployment can also serve a routing SLM - the small routing model osr slm train / osr improve write to a file - as one more strategy in the ensemble, and run the autopilot in-process so the platform's own feedback keeps retraining it:

SettingDefaultEffect
OSR_PLATFORM_SLMnonePath of the model file; it joins the ensemble as the slm strategy (weight OSR_WEIGHTS_SLM, default 1.0).
OSR_PLATFORM_AUTOPILOTfalseRun SelfImprover cycles on a thread: outcomes and remembered prompts become rows, a challenger is trained on a split and promoted only when it beats the champion on the holdout. The promoted model is written to DATA_DIR/autopilot/slm.json and outranks the mounted file after a restart.
OSR_PLATFORM_AUTOPILOT_OFFLINEtrueNo catalogue refresh, dataset download or web search from the API process; false also refreshes OSR_PLATFORM_AUTOPILOT_SOURCES (public dataset presets, comma separated).
OSR_PLATFORM_AUTOPILOT_INTERVAL_SOSR_SLM_AUTOPILOT_INTERVAL_S (3600)Schedule; drift alarms from the outcome stream trigger a cycle early (OSR_SLM_AUTOPILOT_DRIFT_*, rate-limited by OSR_SLM_AUTOPILOT_MIN_GAP_S).
OSR_PLATFORM_AUTOPILOT_MIN_ROWS20A cycle needs at least this many rows before it trains a challenger.

GET /api/v1/learning (any plan) returns the strategy ensemble with its weights, the learner state (persistence, drift resets, quarantined state), per-target outcome statistics, the SLM in service (rows, sources, encoder, calibration, fit history - never the weights) and the autopilot status with its recent champion-vs-challenger reports. controls.slm / controls.autopilot appear in GET /api/v1/governance and GET /api/v1/info. Operators trigger a cycle with POST /api/v1/admin/autopilot/cycle (X-Admin-Token; 404 without an autopilot). Cycles are traced as an autopilot.cycle span with learn.improve / learn.promote events, so they appear in GET /api/v1/events and on /platform/dashboard/events. The dashboard renders all of it at /platform/dashboard/learning.

11. Marketplace#

The marketplace (/marketplace) is where agents, skills, personas, prompts, tools, model profiles and stack templates are shared. Every listing is a manifest the SDK, CLI and this API understand; installing records it on your account and returns the manifest and ready-made snippets, and registry://<slug> references in a stack file resolve to GET /api/v1/registry/{slug}/manifest. Browsing is public; installing, rating and publishing need an account. See the marketplace guide for finding, installing, publishing and the review lifecycle; the endpoints are in the REST API reference under Marketplace.

12. MCP server#

The platform is also a Model Context Protocol server, so an IDE or agent (VS Code, Cursor, Claude, Windsurf) can route, quote and explain through it. GET /mcp describes the server without a key; POST /mcp accepts JSON-RPC 2.0 messages with the same Authorization header as the REST API. Tools: route, estimate, recommend, explain, list_targets, feedback, ask (pro plan and above; routes and executes) and marketplace_search / marketplace_get. Routed calls are metered like /api/v1/route.

Clients that only speak stdio use the CLI as a bridge:

{"mcpServers": {"opensmartroute": {"command": "osr", "args": ["mcp", "--url", "https://<platform>/mcp", "--api-key", "osr_..."]}}}

Per-client configuration, the tool reference and the SDK side are in MCP.md.

13. Dashboard#

The site has two halves on one hostname. The website - landing page, /docs, /models, /vendors, /rankings, /pricing, /marketplace, /compare, /roi, /estimate - needs no account. The platform is everything under /platform: sign-in and sign-up (/platform/login, /platform/signup, password recovery, invitations, /platform/cli/authorize), the dashboard (/platform/dashboard/...), the operator console (/platform/admin/...), the playground (/platform/playground) and the marketplace publish flow (/platform/marketplace/publish). On the hosted service the two are separate deployments; the older paths without the prefix (for example /dashboard/keys, /login or /auth/callback) redirect permanently to their /platform counterpart with the query string intact, so bookmarks, emailed links and OAuth redirect URIs registered before the split keep working.

Signed-in users have a dashboard for the data the API exposes:

PageWhat it shows
/platform/dashboardRequests, tokens and spend for the last 30 days; plan and quota usage; the last 24 hours as a traffic and latency series (GET /api/v1/telemetry/series) and the active alerts (GET /api/v1/alerts).
/platform/dashboard/usagePer-day and per-target usage (GET /api/v1/usage); CSV export of the daily series and both breakdowns.
/platform/dashboard/savingsRouted cost against a baseline model (GET /api/v1/savings?baseline=<id>); CSV export per day and per target for finance.
/platform/dashboard/activityRequest-level activity log: endpoint, target, domain, complexity, tokens, reported outcome - never the prompt text. Rows still in the tracer buffer open a span waterfall with the decision story, outcome and audit records. CSV export of the loaded rows.
/platform/dashboard/eventsThe router's event history for the workspace (GET /api/v1/events, persisted by the platform): a window of 1 hour to 7 days, spans and events with level and stage filters, search by request id / trace id / name, most frequent names, click-through to the request trace, CSV export.
/platform/dashboard/learningHow the router learns (GET /api/v1/learning): strategy weights, outcomes per target, the routing SLM (rows, sources, the targets it ranks, accuracy history, calibration) and the autopilot (schedule, drift, cycles, a retraining-in-progress indicator, champion vs challenger; operators can run a cycle). Fits and cycles export as CSV.
/platform/dashboard/keysCreate, name and revoke API keys.
/platform/dashboard/tenantsTenant constraints and budgets (enterprise).
/platform/dashboard/governanceControls in force, workspace policy editor, budgets and spend, audit status, retention, catalogue data boundaries.
/platform/dashboard/healthDeployment readiness (GET /api/v1/status), the active alerts, HTTP traffic and latency percentiles over time (GET /api/v1/telemetry/series), live counters, routing decisions and latency, circuit breakers, decision cache (enterprise).
/platform/dashboard/notificationsThe alert inbox (firing and resolved episodes, mark read), delivery channels (email, webhook, Slack, Teams) with severity and rule filters and Send test, and the delivery log. The bell in the header shows the unread count.
/platform/dashboard/auditHash-chained decision trail with chain verification (enterprise); export as CSV or as the JSONL the offline verifier reads.
/platform/dashboard/billingPlan, invoices and upgrades.
/platform/dashboard/workspaceThe current workspace: name, slug, plan; create or switch to an organization; organization SSO.
/platform/dashboard/membersMembers, roles and pending invitations of an organization.
/platform/dashboard/listingsMarketplace listings you published and everything you installed.
/platform/dashboard/integrationsMCP configuration for VS Code, Cursor, Windsurf, Claude Code and Claude Desktop with your key.
/platform/dashboard/accountYour sign-in identities, password and browser sessions.

Every table with an Export CSV button downloads exactly the rows shown (UTF-8 with BOM, RFC 4180 quoting, cells that start with =, +, - or @ are prefixed so spreadsheets do not run them as formulas). A failed read shows what it means - session expired, feature not on the plan, budget exhausted with the amounts, rate limit with the retry delay, API unreachable - with a Retry button; an expired session returns to sign-in and back to the page afterwards.

The public pages /platform/playground (route a request in the browser and copy the equivalent curl), /models (every routing target with request, token, cost, latency and health statistics; also GET /api/v1/models, and the reference LLM catalogue with vendor prices and context windows at GET /api/v1/llms), /vendors (one page per LLM vendor with prices, context windows and benchmarks), /rankings (targets ordered by observed quality per domain), /roi (estimated saving against always calling one model), /compare (alternatives side by side) and /marketplace need no account. Machine-readable companions: /llms.txt (documentation map for AI assistants), /feed.xml (Atom feed of releases), /sitemap.xml and /openapi.json.

Operator console#

The people who run a deployment sign in at /platform/admin/login with an operator username and password (POST /api/v1/admin/auth/login returns an osr_op_ session token valid for twelve hours; send it as Authorization: Bearer). The first operator comes from the environment: set OSR_PLATFORM_ADMIN_USERNAME and OSR_PLATFORM_ADMIN_PASSWORD and the API creates it - or resets its password - at start-up as a superadmin. The static OSR_PLATFORM_ADMIN_TOKEN (X-Admin-Token) keeps working for automation and counts as a superadmin. Operators are separate from workspace users.

PageWhat it doesEndpoints
/platform/adminDeployment counters: users, workspaces, keys, tenants, operators, sessions, requests, errors and provider cost of the last seven days, plan mix, sign-in configuration.GET /api/v1/admin/overview, GET /api/v1/admin/plans
/platform/admin/usersSearch users; create one with a personal workspace, plan, optional password and first key (shown once; without a password a reset link is issued so the person chooses one). Per user: workspaces and roles, linked identities, sessions, lifecycle history; rename, disable, reset password or send a reset link, re-send or force the email confirmation, sign out everywhere, delete (workspaces they alone belonged to are disabled and their keys revoked).`GET
/platform/admin/workspacesSearch personal and organization workspaces; create an organization for an owner email. Per workspace: plan, name, slug, disable, delete; members with roles (add by email, change role, remove - the last owner stays); API keys (mint, revoke); tenants (create or replace a validated configuration, delete); policy, SSO and 30-day usage.`GET
/platform/admin/tenantsEvery tenant across workspaces with its configuration.GET /api/v1/admin/tenants
/platform/admin/slmThe routing SLM service: model in service, prompts and outcomes ingested, improvement cycles (champion vs challenger), run a cycle, probe the model with a prompt.GET /api/v1/admin/slm, GET /api/v1/admin/slm/reports, POST /api/v1/admin/slm/cycle, POST /api/v1/admin/slm/predict
/platform/admin/providersModel providers: add an OpenAI-compatible endpoint from a preset, check it (latency, upstream model list), map catalogue targets to upstream models with an optional system prompt, disable or delete; shows the mounted file, what is attached on this replica and the targets that cannot execute yet.`GET
/platform/admin/servicesWhere each domain runs (in the API or as its own service) with a live health probe of every remote service.GET /api/v1/admin/services
/platform/admin/notificationsDeployment alerts as an inbox (readiness, error rate, latency, breakers, drift, autopilot, SLM, tracing), operator channels (email, webhook, Slack, Teams), the delivery log and Evaluate now.GET /api/v1/admin/notifications, POST /api/v1/admin/notifications/read, POST /api/v1/admin/notifications/evaluate, `GET
/platform/admin/operatorsOperator accounts (superadmins create, disable, promote and delete them; the last enabled superadmin cannot be removed), your own password and sessions.`GET
/platform/admin/auditDeployment-wide account-lifecycle log: signups, sign-ins and failed attempts, password and email changes, invitations, roles, removals, deletions, operator actions; filter by action prefix.GET /api/v1/admin/audit-log
/platform/admin/mailEvery email the platform composed with its delivery state; open a message to copy its link. Without OSR_PLATFORM_SMTP_URL this is the delivery channel.GET /api/v1/admin/mail, GET /api/v1/admin/mail/{id}

Every /api/v1/admin/* call is written to the access log with operator:<username> (or operator:admin-token) as the actor. The console is served by the web app but every action is an API call, so the same administration works from scripts with the static token.

14. Privacy and data handling#

  • Prompt text is routed in memory and, when execution is requested, forwarded to the configured provider. The platform stores request metadata (endpoint, target, domain, complexity, token and cost counts), not the prompt or the answer.
  • profile values are hashed before they reach the learners.
  • PII detection runs on every request; contains_pii together with a tenant data_boundary keeps sensitive requests on private or on-prem targets. Details: security model.
  • The public web pages load Google Analytics 4 in consent mode: no analytics or advertising cookies until the visitor accepts the banner, IP addresses truncated, nothing on the dashboard. The choice is stored in localStorage (osr-consent) and can be changed on the privacy page. Besides page views the tag records Core Web Vitals, clicks on outbound links and calls to action, code-snippet copies and the sign-up / sign-in events. Operators of a self-hosted web image leave NEXT_PUBLIC_GA_MEASUREMENT_ID empty to ship no tag at all.

15. Errors#

All errors are JSON: {"detail": "..."} with a conventional status code and an X-Request-Id to quote.

StatusTypical cause
400Invalid objective/constraints/policy keys, unknown target id in a policy, malformed email, an organization-only call on a personal workspace.
401Missing, invalid or revoked API key (WWW-Authenticate: Bearer).
403Feature not in plan (X-Upgrade), role too low, signup disabled.
404Unknown target, tenant, model or listing; enterprise-only feature on a community deployment.
409Duplicate signup, tenant or workspace slug; removing the last owner.
422Body failed validation (the response lists the offending fields).
429Rate limit, daily quota or an exhausted workspace/tenant budget (Retry-After, X-Budget-*).
501/v1/chat/completions on a deployment with no model provider configured.
502The web app could not reach the API.
503/readyz when a dependency is not ready (the body names the failing check).
  • REST API reference - every endpoint, parameter and schema (rendered at /docs/api).
  • Marketplace - find, install and publish agents, skills, personas, prompts and templates.
  • User guide - targets, rules, plans and learning.
  • Deploy - run the same router on your own infrastructure with osr serve.