support-bot. Clients reference the slug at request time; the proxy resolves it to the current preset body before it dispatches upstream.
Every save creates an immutable snapshot in preset_versions, so you can diff, audit, or roll back a bad change without stitching state back together from request logs.
When to use presets
Reach for a preset instead of hardcoding request fields when you want to:- Pin one production configuration — model, temperature, system prompt, provider preferences — behind a stable name that clients don’t need to redeploy to change.
- Ship a config change (a smarter model, a lower temperature, a rewritten system prompt) and be able to instantly point back at the previous snapshot if quality regresses.
- Keep an auditable history of who changed which field, and when, without carrying that history in your own database.
Anatomy
Every preset row is scoped by
team_id; a slug in team A and the same slug in team B are two independent presets. Cross-team reads return preset_not_found — never a distinguishable “wrong team” error.
Reference a preset from the SDK
@routeshift/sdk accepts a preset slug on any chat or chatStream call. Because the preset carries its own canonical model, you can call the proxy without configuring a defaultModel on the client and without passing model on the request — the proxy resolves both from the preset.
defaultModel into a request that carries a preset, so a client-side default won’t accidentally override the preset’s canonical model. Passing model, models, or a :online suffix alongside preset still works — those inputs win, exactly as they do on the raw HTTP endpoint.
If you call chat with neither a preset, a model, a models array, nor a configured defaultModel, the SDK throws before any network call:
Manage presets
Open Routing → Presets in the dashboard, or drive the API directly. Only workspace admins can create, update, or delete presets. Any team member can read them.Create
{ "id": "preset_…", "slug": "support-bot", "version": 1, "proxy_cache_invalidated": true } with 201. On slug collision the API returns 409 preset_slug_taken. Invalid slugs return 400 invalid_preset_slug; unknown models return 400 invalid_preset_model.
Every accepted write (POST, PUT, disable, delete) also invalidates the proxy’s preset cache so the change takes effect on the next request. The response reports the outcome of that step explicitly:
- On success:
"proxy_cache_invalidated": true. - On failure:
"proxy_cache_invalidated": false,"proxy_cache_error": "proxy_cache_invalidation_failed", and"cache_ttl_seconds"— the database write still committed, but the proxy may keep serving the previous preset until the TTL elapses.
Update
PUT is a full-body replace. model, params, system_prompt, and provider_prefs are all required — omit any and the request fails with 400 full_preset_body_required. enabled is optional and preserves its current value when omitted.
Every accepted PUT bumps version by one and appends a preset_versions row. The response is { "slug": "support-bot", "version": 2, "proxy_cache_invalidated": true } — see Proxy cache invalidation for the failure shape.
Delete or disable
?disable=true when you want the slug to fail closed but keep the version history discoverable. A hard DELETE removes the presets row (preset_versions rows persist for audit).
Both variants return { "ok": true, "disabled": <bool>, "proxy_cache_invalidated": true } on success. If the proxy invalidation call fails, the database write still commits and the response reports proxy cache invalidation explicitly.
Proxy cache invalidation
After a successful create, publish, delete, or disable, the dashboard tells the proxy to drop its cached copy of the preset so the next request resolves the new body immediately. Every successful mutation response includes an explicit status for that side effect:
The database write is committed before the invalidation call runs, so a
proxy_cache_invalidated: false response still means the preset was saved. The proxy may serve the previous version for up to cache_ttl_seconds before it re-reads from the database.
How to handle it
- Non-blocking clients (dashboards, background jobs): treat the response as a success — the preset is saved, and the proxy will pick it up within
cache_ttl_seconds. - Change-control flows where new traffic must land on the new version immediately: surface
proxy_cache_errorto the operator and retry the same request (idempotent forPUT,DELETE, andDELETE?disable=true) until you seeproxy_cache_invalidated: true, or wait outcache_ttl_secondsbefore declaring the rollout done. POSTretries: the create already committed, so retrying with the same slug returns409 preset_slug_taken. To force invalidation after a failedPOST, issue aPUTwith the same body — it bumps the version and re-runs invalidation.
proxy_admin_secret_not_configured response (500) means the dashboard is misconfigured and no mutations will invalidate the cache until the secret is set.
Version history
Every accepted create and update writes an immutable snapshot topreset_versions. Snapshots are keyed by (team_id, preset_id, version) and carry the full body (model, params, system_prompt, provider_prefs) plus created_by and created_at.
Preset version history is scoped strictly to the caller’s team. A request for
GET /api/presets/{slug}/versions from a team that doesn’t own {slug} returns the same 404 preset_not_found response as a truly missing preset — teams can’t probe the existence of another team’s presets.List snapshots
Read one snapshot
{version} must be a positive base-10 integer within the PostgreSQL 32-bit integer range (max 2147483647). Leading zeros, negative values, decimals, and values outside the range return 400 invalid_preset_version before any database lookup.
Roll back to a previous version
There’s noPOST /rollback shortcut — read the snapshot you want and replay it as a PUT:
version = 4) whose body is identical to version 2. History stays linear and append-only, which keeps the audit story simple — no version is ever mutated or removed.
Errors
Demo mode
Preset reads (list, detail, version history) transparently substitute the demo team for the caller’s team when demo mode is active — you see the demo workspace’s presets and version history, exactly as they’d render in production. Preset writes (POST, PUT, DELETE) are blocked with 403 so a demo session can’t mutate the real workspace.