Docs
Two ways to route. The embed — one file inside your system, prompts never leave — or the HTTP API. Same engine, same price table, same savings math.
1 · The embed (recommended)
Add the file, wrap your existing model call. Classification runs in your process, the provider call uses your own account, and only savings numbers are reported. Works in Node 18+, serverless functions, edge runtimes and browsers.
import { SmartMem } from "https://getsmartmem.com/sdk/smartmem.js";
const sm = new SmartMem({ apiKey: "sm_live_..." });
// before: openai.chat.completions.create({ model: "gpt-5.6-sol", messages })
const reply = await sm.complete(
{ messages },
(model) => openai.chat.completions.create({ model, messages })
);Options
| Option | Type | Notes |
|---|---|---|
| apiKey | string, required | Your sm_live_ key. Used only to attribute savings to your dashboard; it never unlocks model access. |
| endpoint | string | Where metrics are posted. Defaults to the origin the script was loaded from. |
| baselineModel | string | What savings are measured against. Defaults to openai/gpt-5.6-sol. |
| fallbackModel | string | Retry target if your provider call throws once. Defaults to the baseline model. |
| allowProviders | string[] | Restrict routing to providers you have keys for (openai, anthropic, google, xai). |
| qualityFloor | 0–1 | Raise the minimum capability beyond what the difficulty band implies. |
| maxLatencyMs | int | Prefer models that typically respond within this budget. |
| report | boolean | Set false for a fully offline embed — routes locally, never sends metrics. |
| adaptive | boolean | Default true. Learns from your real outcomes: models that keep failing your traffic get skipped, reliable cheap ones get more work. Tip: throw from your call when an answer is unusable so it counts as a miss. |
| scoutRate | number | Default 0.05. Share of requests that re-test a cheaper model the router stopped using, so it notices when that model improves. |
Methods
- sm.complete({ messages }, call) — Routes locally, calls your function with the chosen model id, reads usage from the response, reports savings. Retries once on the fallback model if your call throws. Returns your function's result.
- sm.route({ messages }) — Routing decision only — the same object the dashboard records, computed in your process. Nothing is sent anywhere.
- sm.complete({ messages }, call, { report: false }) — Run a call without reporting (per-call override).
Routing only, fully offline
Set report: false and nothing ever leaves your infrastructure — the router still picks the model and prices the decision.
import { SmartMem } from "https://getsmartmem.com/sdk/smartmem.js";
const sm = new SmartMem({ apiKey: "sm_live_...", report: false });
const d = sm.route({ messages });
console.log(d.model, d.savings_pct, d.reason); // e.g. google/gemini-3.1-flash-lite 96.6 ...What the embed sends us
Exactly one payload per completed call — numbers only:
POST https://getsmartmem.com/api/public/v1/track
Authorization: Bearer sm_live_...
Content-Type: application/json
{
"routed_model": "google/gemini-3.1-flash-lite",
"input_tokens": 28,
"output_tokens": 40,
"complexity": "simple",
"task_kind": "extraction"
}
// -> { "ok": true, "savings_usd": 0.0004209, "savings_pct": 96.76 }2 · HTTP API
Endpoint
POST /api/public/v1
Authenticate with Authorization: Bearer sm_live_…. Mint a key on your dashboard; it is shown once and stored only as a hash.
Request
curl -X POST https://getsmartmem.com/api/public/v1 \
-H "Authorization: Bearer sm_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "Classify this ticket as billing, bug or feature request: ...",
"output_tokens": 40
}'| Field | Type | Notes |
|---|---|---|
| prompt | string | The task text. Classified in memory and discarded — never stored. Optional if you send input_tokens or complexity. |
| complexity | "simple" | "moderate" | "complex" | Skip automatic classification and set the band yourself. |
| task_kind | string | code, reasoning, writing, extraction, classification, chat. Breaks near-ties toward a model that suits the task. |
| input_tokens | int | Overrides the estimate from the prompt. |
| output_tokens | int | Expected completion length. Defaults to 80% of input. |
| quality_floor | 0–1 | Raise the minimum capability. Never lowers the floor the difficulty band already set. |
| max_latency_ms | int | Prefer models that typically respond within this budget. |
| allow_providers | string[] | Restrict to openai, anthropic, google and/or xai. |
| baseline_model | string | What savings are measured against. Defaults to openai/gpt-5.6-sol. |
Response
{
"model": "google/gemini-3.1-flash-lite",
"provider": "google",
"complexity": "simple",
"complexity_score": 0.21,
"task_kind": "extraction",
"quality_floor": 0.7,
"estimated": {
"input_tokens": 28,
"output_tokens": 40,
"routed_cost_usd": 0.0000141,
"baseline_model": "openai/gpt-5.6-sol",
"baseline_cost_usd": 0.000435,
"savings_usd": 0.0004209,
"savings_pct": 96.76
},
"alternatives": [...],
"reason": "Classified as simple (0.21) — needs capability >= 0.70 ...",
"decision_ms": 3
}Maximum privacy mode
If you would rather not send prompt text at all, classify on your side and send only the shape of the request:
// No prompt leaves your side at all:
{
"complexity": "moderate",
"task_kind": "writing",
"input_tokens": 1200,
"output_tokens": 600,
"quality_floor": 0.85
}Errors
- 401 — missing, malformed or revoked key.
- 400 — invalid body, unknown model, or no prompt, complexity or token count provided.
What gets recorded
Difficulty band, task type, chosen model, baseline model, token counts, both cost figures and the decision time. Nothing else — no prompt text, no completions. The embed cannot send them even by accident: the metrics endpoint accepts numbers only and drops anything else.