One OpenAI-compatible endpoint in front of every model InferMux routes to — plus a small Bearer-authenticated JSON API for the rest of this app.
Pointing an AI agent at this API?
Hand it the LLM-ready Markdown version — self-contained instructions an agent can follow with just a URL and an API key.
All endpoints live under https://infermux.net/api/v1. https://infermux.net is the origin you were given (scheme + host, e.g. https://example.com). Do not add a trailing slash.
That same path is an OpenAI-compatible base URL: point any OpenAI SDK at https://infermux.net/api/v1 with one of our keys and /chat/completions, /models, /audio/transcriptions and /audio/speech work unchanged.
Every endpoint requires an API key sent as a Bearer token:
Authorization: Bearer sk_your_key_here. Keys always start with sk_. A missing or invalid key returns 401.
There are two kinds. Router keys (sk_mr_…, created under API keys) can be scoped to specific models or collections, given an expiry and a spend cap — those are the ones to hand to an app or mint through the login widget. Scope, expiry and cap can be edited later (API keys → Edit) without changing the token; the new rules apply from the next request. A scoped key gets 403 for any other model, and GET /models lists only what it may call. Account keys (sk_…, under Settings) authenticate their owner with no model restrictions.
The model field of a completion takes either an upstream model id (anthropic/claude-3.5-sonnet) or one of your collections as collection/{slug}. A collection is an ordered bucket of models; with auto-route on we try them in order, falling through on failure or rate limit. When a collection mixes vision and text models, an image request is analysed by the vision models first and the result — even an error — is passed to the text models, which write the final answer. The router block in the response says exactly what ran.
Prices are the upstream router's prices plus a platform markup (2% by default, set per provider by an admin) and are exposed on GET /models in USD per token. Each call is charged to the key owner's prepaid credits unless an active subscription covers that model, in which case it draws from the plan's hourly/daily/weekly token allowance instead. 402 means out of credits or the key hit its spend cap. Free models cost nothing either way.
Note on subscriptions: a plan is primarily for Dave (the hosted coding agent, below). Unless the admin enabled "also cover direct API calls" on the plan, calls you make straight to /chat/completions with your own key are billed to credits even while you are subscribed.
Dave is a coding agent whose reasoning loop runs on this server while every tool call (read/write/edit files, ls/find/grep, shell commands) is executed by the small dave client on the user's own machine, inside the project it was started in. The server never touches its own filesystem or shell on the agent's behalf; the client is the security boundary (refuses paths outside the workspace, asks before network / git commit / package installs, never runs sudo).
Dave comes with a subscription. A plan includes one or more AI profiles (e.g. "Dave Pro") — admin-defined bundles of models (LOW / MEDIUM / HIGH coding models, a ROUTER that picks the tier per prompt, VISION for images, optional TTS / STT). Users only ever see the profile name, never the models. Each profile has a token allowance per day, week and month (set per plan); a used-up allowance can be reset with a free reset an admin granted or bought from credits.
Sign-in: dave login runs a device flow (POST /api/v1/dave/auth/start, approve in the browser at /dave/link, POST /api/v1/dave/auth/poll) and receives a Dave key — it opens Dave sessions and calls the /api/v1/dave/* endpoints, and is refused (403 dave_key) everywhere else.
Transport: one WebSocket at https://infermux.net/api/v1/dave/ws (ws:// or wss://), authenticated by sending Authorization: Bearer <key> on the upgrade request (401/402/429 are returned as plain HTTP before the upgrade). Frames are JSON. The client first sends {"type":"hello","protocol":2,"client":{…},"workspace":{"root":"/abs/path","name":"proj"},"profile":"optional-profile-slug"} and gets {"type":"ready","sessionId":…,"profile":{"slug","name"},"profiles":[…],"tools":[…],"billing":{"mode":"subscription","plan":…}} (no subscription → a fatal error with code no_subscription). Then: client → {"type":"prompt","id":"p1","text":"…"} / steer / abort / profile.set / tool.result / tool.stream / pong / bye; server → event (tier, text_delta, thinking_delta, tool_start, tool_end, message_end, …), profile, tool.request ({"callId","method":"fs.read|fs.write|fs.edit|fs.list|fs.glob|fs.grep|proc.exec","params":{…}}), tool.cancel, prompt.done, ping, error, bye. Sending hello with "resume":"<sessionId>" re-attaches to a session after a dropped connection. Model calls made by the loop are metered as source: "dave" against the profile allowance (never credits). GET /api/v1/dave returns the gateway URL, protocol version, tool list, client downloads and your profiles; the full protocol is in src/lib/dave/protocol.ts and docs/DAVE.md.
A browser extension gets a scoped router key for the signed-in user through the login widget's redirect flow, built for chrome.identity.launchWebAuthFlow / browser.identity.launchWebAuthFlow (interactive: true). Open https://infermux.net/widget/connect?widget=<widget id>&redirect_uri=<uri>&state=<opaque>:
- widget — the login widget id (Dashboard → Login widget).
- redirect_uri — https://<extension-id>.chromiumapp.org/… (Chrome/Edge, from chrome.identity.getRedirectURL()) or https://<id>.extensions.allizom.org/… (Firefox). Its origin must be on the widget's allowed origins exactly; an empty list allows no redirect flow at all. Any other value (http, another host, userinfo, a fragment, an unlisted extension) shows an error page and never redirects.
- state — required, up to 512 characters, echoed back unchanged. Generate a random value per attempt and compare it on return.
The user signs in (existing InferMux accounts only — the flow never creates one). A key is minted in their account with the widget's scope, lifetime (keyTtlDays) and per-key spend cap, and the browser is sent to <redirect_uri>#key=sk_mr_…&expiresAt=<ISO 8601>&email=<address>&baseUrl=https://infermux.net/api/v1&state=<state>. Everything rides in the URL fragment (never the query), so the key never reaches a server log; parse it with new URLSearchParams(new URL(responseUrl).hash.slice(1)). Use baseUrl as the OpenAI-compatible base URL. On "Disconnect", call POST /api/v1/widget/revoke with the key as Bearer token. Sign-ins share the widget.login rate limit with the embedded widget.
Responses are JSON unless noted (file download and /audio/speech return raw bytes). Request bodies are JSON (Content-Type: application/json) except file upload and /audio/transcriptions, which are multipart/form-data.
Requests are rate limited per API key. When you exceed a limit you get 429 (or 403 if the limit is configured to block) with an error message and, when applicable, a Retry-After header (seconds). Back off and retry.
The model-router endpoints (/models, /chat/completions, /audio/*, /collections, /usage, /subscription, /credits) return the OpenAI error envelope: { "error": { "message": "...", "type": "...", "code": "..." } }. The older endpoints return a flat { "error": "..." }. Both use matching HTTP status codes (400 bad input, 401 unauthenticated, 402 out of credits, 403 forbidden/out of scope, 404 not found, 413 payload too large, 429 rate limited, 500 server error).
Your base URL is https://infermux.net. Create API keys under Profile → API Keys.
/api/v1/healthConfirms the API is up and your key is valid. Handy as a first call to verify credentials and connectivity.
Request
curl https://infermux.net/api/v1/health \
-H "Authorization: Bearer sk_your_key_here"Response
{
"status": "healthy",
"timestamp": "2026-07-19T12:00:00.000Z",
"uptime": 1234.56,
"version": "1.0.0",
"apiKey": "My key",
"userId": "usr_...",
"message": "API is running successfully"
}/api/v1/statsReturns the calling user together with API-usage counters (requests today / this week / this month, error rate, API-key count).
Request
curl https://infermux.net/api/v1/stats \
-H "Authorization: Bearer sk_your_key_here"Response
{
"user": { "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "createdAt": "..." },
"apiStats": {
"totalApiKeys": 2,
"requestsToday": 14,
"requestsThisWeek": 98,
"requestsThisMonth": 412,
"errorRate": "1.20%",
"errorCount": 5
},
"meta": { "timestamp": "...", "apiKey": "My key" }
}/api/v1/usersLists users. A regular key returns only its own user record; an admin key returns all users with pagination.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| limit | query | integer | no | Page size, 1–100 (default 10). Admin only; ignored for non-admins. |
| offset | query | integer | no | Rows to skip (default 0). Admin only. |
Request
curl "https://infermux.net/api/v1/users?limit=20&offset=0" \
-H "Authorization: Bearer sk_your_key_here"Response
{
"users": [
{ "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "emailVerified": null, "createdAt": "..." }
],
"meta": { "limit": 20, "offset": 0, "total": 1, "apiKey": "My key" }
}/api/v1/usersAdmin-only endpoint scaffold for creating a user. Ships as a stub in this starter — it validates input and echoes it back rather than persisting. Fill in real creation logic before relying on it.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| body | string | yes | New user email. | |
| name | body | string | yes | New user display name. |
| role | body | string | no | 'user' (default) or 'admin'. |
Request
curl -X POST https://infermux.net/api/v1/users \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email":"new@example.com","name":"New User","role":"user"}'Response
{
"message": "User creation endpoint - implementation needed",
"requestedData": { "email": "new@example.com", "name": "New User", "role": "user" },
"apiKey": "My key"
}/api/v1/filesUploads a file and stores its raw bytes. Use this instead of a form/Server Action for any real upload (Server Actions cap the body at ~1MB; this endpoint does not). Send `multipart/form-data` with a single `file` field.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| file | form | file | yes | The file to upload (multipart field name must be "file"). |
Request
curl -X POST https://infermux.net/api/v1/files \
-H "Authorization: Bearer sk_your_key_here" \
-F "file=@./photo.png"Response
{
"id": "fil_...",
"filename": "photo.png",
"url": "/api/v1/files/fil_..."
}/api/v1/files/:idStreams the raw file bytes with the stored Content-Type. Because it is Bearer-gated you cannot put it directly in an `<img src>`; fetch it with the token and build an object URL client-side.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | File id returned by the upload endpoint. |
Request
curl https://infermux.net/api/v1/files/fil_your_file_id \
-H "Authorization: Bearer sk_your_key_here" \
--output downloaded-fileResponse
Raw binary body with the stored `Content-Type` and `Content-Disposition: inline; filename="..."`. Returns `404 { "error": "File not found" }` if unknown./api/v1/files/:idDeletes a file owned by the calling key.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | File id to delete. |
Request
curl -X DELETE https://infermux.net/api/v1/files/fil_your_file_id \
-H "Authorization: Bearer sk_your_key_here"Response
{ "deleted": true } // { "deleted": false } with status 404 if not found / not owned/api/v1/modelsEvery model this key may use, in the OpenAI `list` shape, with prices already including the platform markup. Your collections are listed too, as `collection/{slug}` pseudo-models you can pass straight to /chat/completions.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| search | query | string | no | Substring match on id, name or description. |
| category | query | string | no | Comma-separated tags to require, e.g. `programming,vision`. |
| input_modality | query | string | no | Comma-separated required input modalities, e.g. `image`. |
| free | query | boolean | no | `true` returns only zero-cost models. |
| tools | query | boolean | no | `true` returns only models supporting tool calling. |
| min_context | query | integer | no | Minimum context window in tokens. |
| max_prompt_price | query | number | no | Maximum prompt price in USD per million tokens. |
| sort | query | string | no | `name` (default), `newest`, `price-asc`, `price-desc`, `context-desc`, `context-asc`. |
| limit | query | integer | no | Page size (default: all matches). |
| offset | query | integer | no | Rows to skip (default 0). |
| include_collections | query | boolean | no | `false` omits your collections from the list. |
Request
curl "https://infermux.net/api/v1/models?category=vision&free=true" \
-H "Authorization: Bearer sk_your_key_here"Response
{
"object": "list",
"data": [
{
"id": "collection/free-vision",
"object": "model",
"owned_by": "you",
"name": "Free vision models",
"collection": { "id": "col_...", "auto_route": true }
},
{
"id": "anthropic/claude-3.5-sonnet",
"object": "model",
"owned_by": "openrouter",
"name": "Anthropic: Claude 3.5 Sonnet",
"context_length": 200000,
"architecture": { "input_modalities": ["text", "image"], "output_modalities": ["text"] },
"categories": ["vision", "tools", "long-context"],
"pricing": { "prompt": "0.00000306", "completion": "0.0000153", "request": "0", "image": "0" }
}
],
"meta": { "total": 2, "returned": 1, "collections": 1 }
}/api/v1/chat/completionsThe main endpoint. Accepts a standard OpenAI chat-completions body. `model` is either an upstream model id (`anthropic/claude-3.5-sonnet`) or one of your collections as `collection/{slug}`. With auto-route on, a collection is walked in order and falls through to the next model on failure or rate limit; if the collection mixes vision and text models, an image request goes to the vision models first and their result — even an error — is handed to the text models, which produce the answer. Set `stream: true` for a standard SSE stream.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| model | body | string | yes | Model id, or `collection/{slug}`. |
| messages | body | array | yes | OpenAI messages. Content may be a string or an array of `text`/`image_url` parts. |
| stream | body | boolean | no | `true` streams `text/event-stream` chunks, ending with a `router.summary` frame and `[DONE]`. |
| tools | body | array | no | Tool definitions. Auto-route only considers tool-capable models when present. |
| temperature | body | number | no | Passed through to the upstream model, like any other sampling parameter. |
Request
curl -X POST https://infermux.net/api/v1/chat/completions \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"model":"collection/free-vision","messages":[{"role":"user","content":"What is in this image?"}]}'Response
{
"id": "gen-...",
"object": "chat.completion",
"model": "meta-llama/llama-3.3-70b-instruct",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }],
"usage": { "prompt_tokens": 42, "completion_tokens": 128, "total_tokens": 170 },
"router": {
"requested": "collection/free-vision",
"resolved": "meta-llama/llama-3.3-70b-instruct",
"collection": "free-vision",
"auto_route": true,
"attempts": 2,
"stages": ["vision", "primary"],
"charged_usd": 0.00021,
"covered_by_subscription": false
}
}/api/v1/audio/transcriptionsTurns an audio file into text, exactly like OpenAI's `/audio/transcriptions`. `model` must be a transcription model — they are listed by `GET /models?category=transcription` (e.g. `qwen/qwen3-asr-0.6b`, `openai/whisper-large-v3-turbo`). Same rules as chat: the key must be scoped to the model, and the call is charged to your credits at the upstream price plus markup. Transcription models are priced per second of audio (`pricing.prompt` on `GET /models` is USD per second); the upstream's exact cost is used when it reports one.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| file | form | file | yes | The audio (mp3, wav, m4a, ogg, webm, flac, …), max 25 MB. |
| model | form | string | yes | A transcription model id. Collections are not accepted here. |
| language | form | string | no | Spoken language hint (ISO-639-1, e.g. `en`). |
| prompt | form | string | no | Optional context/vocabulary hint, passed upstream. |
| response_format | form | string | no | `json` (default) → `{ text, usage }`; `verbose_json` adds `language`, `duration` and `segments`; `text` returns the transcript as `text/plain`. `srt`/`vtt` are not supported (400). |
| temperature | form | number | no | Sampling temperature, passed upstream. |
Request
curl -X POST https://infermux.net/api/v1/audio/transcriptions \
-H "Authorization: Bearer sk_your_key_here" \
-F model=qwen/qwen3-asr-0.6b \
-F file=@recording.mp3Response
{
"text": "Hello from InferMux.",
"usage": { "seconds": 1.7, "cost": 0.000005661 },
"router": {
"requested": "qwen/qwen3-asr-0.6b",
"resolved": "qwen/qwen3-asr-0.6b",
"charged_usd": 0.000005661
}
}/api/v1/audio/speechTurns text into audio, exactly like OpenAI's `/audio/speech`: send JSON, get the raw audio bytes back with the matching `Content-Type` (`audio/mpeg` for mp3). `model` must be a text-to-speech model — listed by `GET /models?category=speech` (e.g. `hexgrad/kokoro-82m`). The key must be scoped to the model. Speech models are priced per input character (`pricing.prompt` on `GET /models` is USD per character): the charge is characters × that price (plus any per-request fee), because the upstream reports no cost for speech. Models priced per *output* audio token cannot be metered up front and are refused with 400.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| model | body | string | yes | A text-to-speech model id. Collections are not accepted here. |
| input | body | string | yes | The text to speak, up to 10,000 characters. |
| voice | body | string | no | Voice id; model-specific (e.g. `af_heart` for kokoro). |
| response_format | body | string | no | `mp3` (default), `opus`, `aac`, `flac`, `wav` or `pcm` — whichever the model supports. |
| speed | body | number | no | Playback speed multiplier, e.g. `1.0`. |
Request
curl -X POST https://infermux.net/api/v1/audio/speech \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"model":"hexgrad/kokoro-82m","input":"Hello from InferMux.","voice":"af_heart","response_format":"mp3"}' \
-o hello.mp3Response
(binary audio body, streamed)
Content-Type: audio/mpeg
X-Usage-Cost: 0.0000124000
X-Usage-Characters: 20
X-Router-Model: hexgrad/kokoro-82m/api/v1/collectionsYour collections and their ordered members. Pass the `model` value of any of them to /chat/completions to route through it.
Request
curl https://infermux.net/api/v1/collections \
-H "Authorization: Bearer sk_your_key_here"Response
{
"object": "list",
"data": [
{
"id": "col_...",
"model": "collection/free-vision",
"name": "Free vision models",
"slug": "free-vision",
"auto_route": true,
"models": [
{ "position": 0, "id": "qwen/qwen-2-vl-7b-instruct", "vision": true, "free": true, "available": true },
{ "position": 1, "id": "meta-llama/llama-3.3-70b-instruct", "vision": false, "free": true, "available": true }
]
}
]
}/api/v1/usageTokens in/out, request counts and spend for the hour, day, week, month, year and all-time, each with the instant its window resets. With `?window=` you get one window plus a bucketed series and the top models by spend.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| window | query | string | no | One of `hour`, `day`, `week`, `month`, `year`, `ever`. Omit for all windows at once. |
Request
curl "https://infermux.net/api/v1/usage?window=day" \
-H "Authorization: Bearer sk_your_key_here"Response
{
"usage": {
"day": {
"window": "day",
"requests": 128,
"successes": 126,
"errors": 2,
"promptTokens": 402113,
"completionTokens": 88420,
"totalTokens": 490533,
"costUsd": 1.284,
"since": "2026-07-19T00:00:00.000Z",
"resetsAt": "2026-07-20T00:00:00.000Z"
}
},
"credits": { "balanceUsd": 18.72, "lifetimeToppedUpUsd": 20, "lifetimeSpentUsd": 1.28 }
}/api/v1/subscriptionYour Dave plan and, for every AI profile it includes, the token limit, usage, what is left and exactly when it resets — per day, week and month.
Request
curl https://infermux.net/api/v1/subscription \
-H "Authorization: Bearer sk_your_key_here"Response
{
"subscribed": true,
"plan": { "id": "pln_...", "name": "Pro", "price_usd": 50 },
"subscription": { "id": "sub_...", "status": "active", "started_at": "2026-10-01T09:00:00.000Z", "current_period_end": "2026-11-01T09:00:00.000Z" },
"profiles": [
{
"slug": "dave-pro", "name": "Dave Pro", "exhausted": false,
"allowances": [
{ "window": "day", "token_limit": 2000000, "tokens_used": 90200, "tokens_remaining": 1909800, "resets_at": "2026-10-10T00:00:00.000Z" },
{ "window": "week", "token_limit": null, "tokens_used": 402000, "tokens_remaining": null, "resets_at": "2026-10-12T00:00:00.000Z" },
{ "window": "month", "token_limit": 40000000, "tokens_used": 1402000, "tokens_remaining": 38598000, "resets_at": "2026-11-01T00:00:00.000Z" }
]
}
]
}/api/v1/daveEverything a `dave` client (or someone setting one up) needs: the WebSocket gateway URL and protocol version, the tools the hosted agent can call, download links for the client binaries (when CI has built them), your plan and the AI profiles it includes (names only — never the models behind them).
Request
curl https://infermux.net/api/v1/dave \
-H "Authorization: Bearer <dave key>"Response
{
"protocol": 2,
"gateway": { "url": "wss://example.com/api/v1/dave/ws", "path": "/api/v1/dave/ws", "auth": "Authorization: Bearer <key from `dave login`> on the upgrade request, or (browsers) subprotocols ["dave.v2", "dave.bearer.<key>"]" },
"tools": ["read", "write", "edit", "ls", "find", "grep", "bash"],
"subscription": { "plan": "Pro" },
"profiles": [
{ "slug": "dave-lite", "name": "Dave Lite", "description": "Fast and frugal.", "default": true },
{ "slug": "dave-pro", "name": "Dave Pro", "description": "Our strongest setup.", "default": false }
],
"downloads": [{ "arch": "macosx-arm", "label": "macOS (Apple silicon)", "filename": "dave", "url": "https://…/dave" }],
"downloads_available": true,
"downloads_note": null,
"install": { "source": "bun run dave/src/main.ts login --server <BASE_URL>", "binary": "dave login --server <BASE_URL> && dave" }
}/api/v1/dave/auth/startBegins the device sign-in flow. Returns a secret `device_code` (keep it in the client) and a short `user_code`; send the user to `verification_path_complete` (on this host) where they sign in and approve the code. Then poll `/dave/auth/poll`. Approval requires an active Dave subscription.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| client | body | object | no | `{ name, host, platform }` — shown on the approval page so the user recognises the device. |
Request
curl -X POST https://infermux.net/api/v1/dave/auth/start \
-H "Content-Type: application/json" \
-d '{"client":{"name":"dave 0.2.0","host":"my-laptop","platform":"darwin/arm64"}}'Response
{
"device_code": "9f2c…(64 hex)",
"user_code": "KJ4F-Q2RD",
"verification_path": "/dave/link",
"verification_path_complete": "/dave/link?code=KJ4F-Q2RD",
"expires_in": 600,
"interval": 3
}/api/v1/dave/auth/pollPoll every `interval` seconds with the `device_code`. Returns `pending` until the user approves, then `approved` with the Dave key — handed out exactly once — or `denied` / `expired`.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| device_code | body | string | yes | From `/dave/auth/start`. |
Request
curl -X POST https://infermux.net/api/v1/dave/auth/poll \
-H "Content-Type: application/json" \
-d '{"device_code":"9f2c…"}'Response
{ "status": "approved", "key": "sk_mr_…", "key_expires_at": "2027-01-07T09:00:00.000Z" }/api/v1/dave/auth/logoutRevokes the Dave key making the call. Other key kinds are left alone.
Request
curl -X POST https://infermux.net/api/v1/dave/auth/logout \
-H "Authorization: Bearer <dave key>"Response
{ "revoked": true }/api/v1/dave/usageWhat `dave usage` / `/usage` shows: your plan, and per AI profile the tokens used and the limit for each window with its reset time, whether it is used up and when it frees up again, the free resets you can redeem (per profile, and `free_resets` in total) and the price of a bought reset — plus your credit balance and `buy_resets_path`, the page on this site where resets are bought.
Request
curl https://infermux.net/api/v1/dave/usage \
-H "Authorization: Bearer <dave key>"Response
{
"plan": { "id": "pln_…", "name": "Pro", "renewsAt": "2026-11-09T07:00:00.000Z" },
"balance_usd": 7,
"free_resets": 1,
"buy_resets_path": "/dashboard/subscription#allowances",
"profiles": [
{
"slug": "dave-pro", "name": "Dave Pro", "description": "Our strongest setup.", "default": false,
"exhausted": true, "available_again_at": "2026-10-10T00:00:00.000Z", "last_reset_at": null,
"windows": [
{ "window": "day", "limit": 1500, "used": 3750, "remaining": 0, "resets_at": "2026-10-10T00:00:00.000Z" },
{ "window": "week", "limit": null, "used": 3750, "remaining": null, "resets_at": "2026-10-12T00:00:00.000Z" },
{ "window": "month", "limit": 5000000, "used": 3750, "remaining": 4996250, "resets_at": "2026-11-01T00:00:00.000Z" }
],
"free_resets": 1,
"reset_price_usd": 3
}
]
}/api/v1/dave/resetsClears a profile's day, week and month usage for you. `mode: "auto"` (default) uses a free reset if you have one and otherwise buys one from your credits; `"free"` only uses a free reset; `"purchase"` always buys.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| profile | body | string | yes | Profile slug or name. |
| mode | body | string | no | `auto` | `free` | `purchase`. |
Request
curl -X POST https://infermux.net/api/v1/dave/resets \
-H "Authorization: Bearer <dave key>" -H "Content-Type: application/json" \
-d '{"profile":"dave-pro","mode":"free"}'Response
{
"reset": true, "profile": "Dave Pro", "paid_with": "free_reset", "price_usd": 0,
"windows": [{ "window": "day", "limit": 1500, "used": 0, "resets_at": "2026-10-10T00:00:00.000Z" }]
}/api/v1/dave/sessionsThe caller's recent Dave sessions, most recent first: AI profile, workspace name/root as reported by the client, client version/platform, prompt and tool-call counters (including how many calls the client refused) and status.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| limit | query | number | no | Max rows (1–100, default 20). |
Request
curl "https://infermux.net/api/v1/dave/sessions?limit=10" \
-H "Authorization: Bearer sk_your_key_here"Response
{
"object": "list",
"data": [
{
"id": "ses_…", "status": "ended", "live": false,
"profile": "Dave Pro", "thinking_level": "medium",
"workspace": { "root": "/home/me/project", "name": "project" },
"client": { "version": "0.1.0", "platform": "darwin/arm64" },
"prompts": 3, "tool_calls": 17, "tool_calls_denied": 1,
"started_at": "2026-08-20T09:00:00.000Z", "last_seen_at": "2026-08-20T09:12:41.000Z",
"ended_at": "2026-08-20T09:12:41.000Z", "end_reason": "client_bye"
}
]
}/api/v1/widget/revokeRevokes the key making the call, if it was minted by a login widget (the embedded widget or the `/widget/connect` extension redirect flow). Use it for an app's "Log out" or an extension's "Disconnect". Expired or spent keys can still revoke themselves, and repeating the call is harmless. Other key kinds are left alone.
Request
curl -X POST https://infermux.net/api/v1/widget/revoke \
-H "Authorization: Bearer sk_mr_widget_key"Response
{ "revoked": true }/api/v1/creditsPrepaid balance and lifetime totals for the key owner, plus this key's own scope, spend and spend limit.
Request
curl https://infermux.net/api/v1/credits \
-H "Authorization: Bearer sk_your_key_here"Response
{
"balance_usd": 18.72,
"lifetime_topped_up_usd": 20,
"lifetime_spent_usd": 1.28,
"key": {
"name": "Production app",
"scope": "collections",
"spend_limit_usd": 5,
"spent_usd": 0.42,
"expires_at": "2026-07-26T12:00:00.000Z"
}
}