Platform guide
Authentication, POST /api/v1/route, the OpenAI-compatible endpoint, feedback, plans, tenants, dashboard and errors.
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#
- Sign up at
/platform/signupwith 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. - The key is shown once. Store it in a secret manager. Keys are created and revoked in the dashboard
(
/platform/dashboard/keys) or withPOST /api/v1/keysandDELETE /api/v1/keys/{key_id}. - 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:
| Step | How | Endpoints |
|---|---|---|
| Email confirmation | Signup 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} |
| Invitations | POST /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 history | The person's own sign-ins, password and email changes and memberships, newest first. | GET /api/v1/me/audit |
| Delete account | Delete 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 started | The 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:
| Step | Endpoint | Notes |
|---|---|---|
| CLI asks for a code | POST /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 up | GET /api/v1/auth/device/{user_code} | signed in; shows client name, status and expiry |
| Browser decides | POST /api/v1/auth/device/approve / deny {user_code, name} | approving needs the admin role and a free key slot in the plan |
| CLI polls | POST /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:
| Field | Type | Meaning |
|---|---|---|
text | string, required | The user request. |
history | list of {role, content} | Prior turns; the router reads them for intent, task type and session affinity. |
context | object | Free-form facts (app, session_id, user_locale, ...). Keys starting with _ are dropped. |
objective | object | Relative 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. |
constraints | object | Hard constraints, never traded off: see below. |
profile | object | Opaque user/tenant features (locale, expertise, preferences). Hashed into the learners; never logged raw. |
kinds | list | Restrict to target kinds: llm, agent, skill, persona, tool, human. |
top_k | int, default 3 | How many alternatives to return. |
plan | bool | Build a persona → skill → model plan instead of choosing a single target (pro plan and above). |
execute | bool | Route and run the chosen target or plan; the output is returned in result (pro and above). |
Constraints (constraints object):
| Key | Effect |
|---|---|
max_cost_per_1k | Drop targets whose price per 1k tokens is above this. |
max_latency_ms | Drop targets slower than this. |
preferred_max_latency_ms | Soft target: targets whose observed p90 latency is above it are penalised, not dropped. |
region | Only targets served from this region. |
data_boundary | Only targets at least this strict (public < private < on_prem). |
contains_pii | Force the PII handling on or off (null lets the sensitivity signal decide). |
allow_targets / deny_targets | Explicit allow / deny lists of target ids. |
allowed_kinds | Same as kinds. |
require_tools | Only 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) withproviders.<name>(base_url,api_keyorapi_key_env, optionalapi_key_header,extra_headers,timeout_s,max_retries) andmodels.<target_id>(provider,model, optionalsystem_promptand 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>/modelswith 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 withinOSR_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#
| Header | On | Meaning |
|---|---|---|
X-Request-Id | every response | The id to quote in feedback and support requests; echoed back when you send one. |
Server-Timing | every response | app;dur=<ms> - time spent inside the platform. |
X-OSR-Target | /v1/chat/completions | The target id that answered. |
X-OSR-Confidence | /v1/chat/completions | The router's confidence in that choice. |
Retry-After | 429 | Seconds until the per-minute window, the daily quota or the budget period resets. |
X-Quota-Limit | 429 (daily) | Your plan's requests per day. |
X-Budget-Limit, X-Budget-Used, X-Budget-Period | 429 (budget) | The exhausted workspace or tenant budget in USD and its period (daily / monthly). |
X-Upgrade: true | 403 | The feature exists but is not in your plan. |
ETag, Cache-Control | public catalogue reads | Conditional 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:
| Field | Meaning |
|---|---|
model | auto (default) lets the router choose; a target id pins that target. |
models | Candidate list: the router chooses among them and falls back down the list when a target fails. |
osr.objective / osr.constraints | Same as on /api/v1/route. |
osr.tenant | Tenant slug (or the X-OSR-Tenant header); applies the tenant's constraints (enterprise). |
osr.plan | true/false to force or suppress plan composition; omitted = whatever the plan tier allows. |
osr.fallbacks | false disables the automatic retry on the next best target (default true). |
stream | true 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#
| Plan | Requests / day | Requests / min | Keys | Features |
|---|---|---|---|---|
| free | 500 | 30 | 2 | route + trace, OpenAI-compatible proxy |
| pro | 20,000 | 300 | 10 | + plans (plan/execute), feedback |
| enterprise | 500,000 | 3,000 | 100 | + 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; waitRetry-Afterseconds.429 daily quota exhausted- the day's budget;X-Quota-Limitshows the ceiling.403withX-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:
| Action | Role |
|---|---|
| Route, read usage, savings and activity | member |
| Create and revoke API keys, change tenants, invite and remove members, rename the workspace | admin |
Grant or remove owner, configure organization SSO, delete the connection | owner |
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:
| Block | Contents |
|---|---|
controls | What the deployment enforces: input_guard, pii_redaction, steering_strip, metrics, tracing, audit, health_breakers, decision_cache, learning (persisted or in-process). |
audit | Whether the hash-chained audit trail is on, its length and the result of the last verification. |
retention_days | How long usage rows are kept before the background sweep purges them. |
policy, policy_keys, budgets, spend | The workspace policy, its accepted keys, budget lines with used and remaining amounts, spend today and this month. |
quota | Requests today against the plan's daily and per-minute limits. |
tenants | Each tenant with its monthly spend and budget lines. |
catalogue | Targets 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:
| Endpoint | Meaning |
|---|---|
GET /healthz | Process is up: edition, version, target count, uptime. |
GET /readyz | 200 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 /metrics | The 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#
| Endpoint | Returns |
|---|---|
GET /api/v1/telemetry/series?window=24h | The 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/alerts | Active 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:
| Kind | Target | Delivery |
|---|---|---|
email | An address | Through the platform mailer (OSR_PLATFORM_SMTP_URL, otherwise the outbox at /platform/admin/mail). |
webhook | An https:// URL | POST 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. |
slack | An incoming-webhook URL | Block Kit message with severity, rule, value against threshold and a link to the page that fixes it. |
teams | An incoming-webhook URL | MessageCard 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.
| Endpoint | Purpose |
|---|---|
GET /api/v1/notifications?state=firing&unread=true&limit=100 | The workspace inbox (items) and unread counts per severity (unread). |
GET /api/v1/notifications/unread | Unread counts only - the dashboard bell polls this. |
POST /api/v1/notifications/read | {"ids": [...]} marks those read, {"ids": null} marks everything read. |
| `GET | POST /api/v1/notifications/channels` |
| `PATCH | DELETE /api/v1/notifications/channels/{id}` |
POST /api/v1/notifications/channels/{id}/test | Deliver a test notification and return ok, status, error. |
GET /api/v1/notifications/deliveries | The last delivery attempts (channel, event, attempt, status, error). |
| `GET | POST /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:
| State | Service | Setting |
|---|---|---|
| Accounts, users, keys, tenants, policies, usage, feedback, marketplace | PostgreSQL | OSR_PLATFORM_DATABASE_URL |
| Rate-limit windows, learner state (enterprise) | Redis | OSR_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:
| Endpoint | Returns |
|---|---|
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=200 | Events 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/status | Public 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:
| Setting | Default | Effect |
|---|---|---|
OSR_PLATFORM_METRICS_PUBLIC | true | false restricts /metrics to requests carrying X-Admin-Token. |
OSR_PLATFORM_HTTP_CACHE_MAX_AGE_S | 60 | Cache-Control lifetime of public reads; 0 sends no-store. |
OSR_PLATFORM_ROUTE_CACHE_TTL_S | 0 | Reuse a routing decision for identical requests within this window (0 = off). |
OSR_PLATFORM_RETENTION_DAYS | 365 | Usage rows older than this are purged every six hours; 0 keeps everything. |
OSR_PLATFORM_TELEMETRY_STORE | true | Persist 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_DAYS | 14 | Persisted 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:
| Setting | Default | Effect |
|---|---|---|
OSR_PLATFORM_SLM | none | Path of the model file; it joins the ensemble as the slm strategy (weight OSR_WEIGHTS_SLM, default 1.0). |
OSR_PLATFORM_AUTOPILOT | false | Run 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_OFFLINE | true | No 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_S | OSR_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_ROWS | 20 | A 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:
| Page | What it shows |
|---|---|
/platform/dashboard | Requests, 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/usage | Per-day and per-target usage (GET /api/v1/usage); CSV export of the daily series and both breakdowns. |
/platform/dashboard/savings | Routed cost against a baseline model (GET /api/v1/savings?baseline=<id>); CSV export per day and per target for finance. |
/platform/dashboard/activity | Request-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/events | The 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/learning | How 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/keys | Create, name and revoke API keys. |
/platform/dashboard/tenants | Tenant constraints and budgets (enterprise). |
/platform/dashboard/governance | Controls in force, workspace policy editor, budgets and spend, audit status, retention, catalogue data boundaries. |
/platform/dashboard/health | Deployment 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/notifications | The 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/audit | Hash-chained decision trail with chain verification (enterprise); export as CSV or as the JSONL the offline verifier reads. |
/platform/dashboard/billing | Plan, invoices and upgrades. |
/platform/dashboard/workspace | The current workspace: name, slug, plan; create or switch to an organization; organization SSO. |
/platform/dashboard/members | Members, roles and pending invitations of an organization. |
/platform/dashboard/listings | Marketplace listings you published and everything you installed. |
/platform/dashboard/integrations | MCP configuration for VS Code, Cursor, Windsurf, Claude Code and Claude Desktop with your key. |
/platform/dashboard/account | Your 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.
| Page | What it does | Endpoints |
|---|---|---|
/platform/admin | Deployment 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/users | Search 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/workspaces | Search 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/tenants | Every tenant across workspaces with its configuration. | GET /api/v1/admin/tenants |
/platform/admin/slm | The 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/providers | Model 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/services | Where 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/notifications | Deployment 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/operators | Operator accounts (superadmins create, disable, promote and delete them; the last enabled superadmin cannot be removed), your own password and sessions. | `GET |
/platform/admin/audit | Deployment-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/mail | Every 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.
profilevalues are hashed before they reach the learners.- PII detection runs on every request;
contains_piitogether with a tenantdata_boundarykeeps 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 leaveNEXT_PUBLIC_GA_MEASUREMENT_IDempty 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.
| Status | Typical cause |
|---|---|
400 | Invalid objective/constraints/policy keys, unknown target id in a policy, malformed email, an organization-only call on a personal workspace. |
401 | Missing, invalid or revoked API key (WWW-Authenticate: Bearer). |
403 | Feature not in plan (X-Upgrade), role too low, signup disabled. |
404 | Unknown target, tenant, model or listing; enterprise-only feature on a community deployment. |
409 | Duplicate signup, tenant or workspace slug; removing the last owner. |
422 | Body failed validation (the response lists the offending fields). |
429 | Rate 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. |
502 | The web app could not reach the API. |
503 | /readyz when a dependency is not ready (the body names the failing check). |
Related#
- 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.