Descles documentation
BYOK model gateway + control plane
Descles sits between your agent and its model provider. You keep your provider endpoint and key (or store the key with us); your traffic gets authenticated, attributed to a person, recorded, budgeted, and gated by policy — without changing your client code.
Overview
| You get | A stable gateway URL per provider, a control plane that attributes every call to the agent that made it, and policy that can deny or pause dangerous tool calls. |
|---|---|
| You keep | Your provider account and API key (two modes, see Provider keys). |
| You change | Only the base_url your client points at. |
Quickstart
1 · Claim the workspace and agent key
Create a Clerk account, then submit the test launch form. You receive a data key bound to your primary agent (1,000 free middleware requests, 30 days). Open the console with the same Clerk session; the key is shown there for the rest of the current browser tab.
2 · Choose how provider keys flow
Hosted BYOK: save a provider endpoint and key once under Console → Providers. Point the client at https://<provider>.gw.descles.com/v1 and authenticate only with the Descles agent key. This is the recommended team setup.
export DESCLES_BASE_URL=https://deepseek.gw.descles.com/v1
export DESCLES_API_KEY=<your-descles-key>
curl "$DESCLES_BASE_URL/chat/completions" \
-H "Authorization: Bearer $DESCLES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hello"}]}'Per-request BYOK: send the provider endpoint and key on each request. Descles forwards the credential for that call without saving it:
curl "https://api.gw.descles.com/v1/chat/completions" \
-H "Authorization: Bearer <your-descles-key>" \
-H "X-Descles-Provider-Base-URL: https://api.deepseek.com/v1" \
-H "X-Descles-Provider-Key: <your-provider-key>" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-chat","messages":[{"role":"user","content":"Hello"}]}'OpenAI SDK (TypeScript):
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.gw.descles.com/v1",
apiKey: process.env.DESCLES_KEY, // your Descles token
defaultHeaders: {
"X-Descles-Provider-Base-URL": "https://api.deepseek.com/v1",
"X-Descles-Provider-Key": process.env.PROVIDER_KEY,
},
});Hermes Agent with the hosted DeepSeek credential:
# ~/.hermes/.env DEEPSEEK_BASE_URL=https://deepseek.gw.descles.com/v1 DEEPSEEK_API_KEY=<your-descles-agent-key> # then select deepseek-chat in: hermes model
/v1/models before accepting a custom endpoint. Descles routes that probe through the same organization-scoped BYOK credential as chat requests.Claude Code uses hosted BYOK with the Anthropic-native endpoint:
export ANTHROPIC_BASE_URL=https://anthropic.gw.descles.com export ANTHROPIC_AUTH_TOKEN=<your-descles-agent-key> # store your Anthropic key once in the console Providers page (hosted BYOK); # no provider key stays on this machine.
3 · Load a starter policy
Open Console → Policy, choose Load starter policy, review the tool names, and save. Tool names must match what your runtime declares; the starter includes common Claude Code and terminal-style names.
defaults:
tools:
Bash: require_approval
Edit: require_approval
Write: deny
terminal: require_approval
execute_code: deny
shell.rm: deny
budget:
daily_usd: 10Budgets are enforced per UTC day and can set a USD cap (daily_usd), a token cap (daily_tokens, counting input + output + cached), or both. Token caps are price-independent — use them for router aggregators or any endpoint whose pricing you know better than a generic estimate.
Endpoints & protocols
One wildcard domain serves every provider you route through. The subdomain names the provider; the tenant is identified by your token.
https://<provider>.gw.descles.com/v1/chat/completions OpenAI-compatible https://<provider>.gw.descles.com/v1/responses Responses API https://<provider>.gw.descles.com/anthropic/v1/messages Anthropic Messages https://<provider>.gw.descles.com/v1/models Model list
| You call today | Switch base_url to |
|---|---|
api.deepseek.com | deepseek.gw.descles.com |
api.openai.com | openai.gw.descles.com |
api.anthropic.com | anthropic.gw.descles.com |
openrouter.ai/api/v1 | openrouter.gw.descles.com |
| your own vLLM | anything.gw.descles.com (name it what you like) |
Model routing: the request body is forwarded as-is; your provider decides which models it serves. No registry, no model mapping to maintain.
Router companies & custom endpoints. Descles never needs to know your provider — any endpoint that speaks the OpenAI protocol works: aggregators (OpenRouter, OneAPI-style routers), LiteLLM proxies, and self-hosted vLLM / SGLang / TGI. In Console → Providers choose custom / router / self-hosted and give the endpoint a name; the name becomes your subdomain, so https://<your-name>.gw.descles.com/v1 routes to that base URL with the stored key. For a hostname the name must be lowercase letters, digits and hyphens only; if your label cannot be a hostname (dots, underscores, long vendor strings) — or you keep no stored credential — use the per-request X-Descles-Provider-Base-URL + X-Descles-Provider-Key headers against api.gw.descles.com instead. Under an explicit-allowlist egress firewall, add the endpoint host once and it is reachable like any provider.
Cost on unknown models. Your model string is forwarded untouched, so Descles may not have a price-table entry for it — the cost_usd estimate then falls back to a default price and can be off. For custom or aggregator endpoints prefer token budgets (daily_tokens), which are price-independent.
Provider keys — two modes
Mode A · Hosted BYOK (recommended for teams)
Your org stores the provider credential once, encrypted at rest (AES-GCM, never returned by any list endpoint). Employees authenticate with plain Descles tokens — no provider key on their machines, instant revocation when you rotate the org key.
# console → Providers → add { provider, base_url, api_key }
# then your client needs only:
baseURL: "https://deepseek.gw.descles.com/v1"
apiKey: "<descles token>" // no provider headersMode B · Per-request BYOK
Your key never touches Descles storage; you send it on each request (see Quickstart). Use this while evaluating or when your security policy forbids storing provider keys with a third party.
Console — manage the workspace
Open the Descles console and sign in with Clerk. Your account resolves its workspace automatically; there is no separate admin token to paste.
- Agents — the single identity view: person groups (members) with their primary agent, plus service agents per group. Every key hangs on an agent; issuing another key rotates the previous one.
- Providers — store your upstream endpoint + key (Mode A).
- Budgets — daily USD limits per agent or group. Over budget = the next request is blocked, not just charted.
- Model traces — every call: employee, agent, model, tokens, cost, latency, policy decision.
- Approvals — dangerous tool calls pause here until an operator approves or denies.
Policy — writing rules
Policy decides what an agent may do with the tools it proposes. It is evaluated on every model call at the gateway, before anything executes:
deny— the tool call is stripped from the model response; the runtime never sees it.require_approval— the call is replaced with a pending-approval placeholder and an approval is opened for an operator.allow(or no rule) — the call passes.
Most-restrictive wins: deny > require_approval > allow. Tool names are matched with . and _ normalized (shell.rm matches what providers may emit as shell_rm), and trailing * wildcards are supported.
# policy.yaml — YAML or JSON. Serve it with DESCLES_POLICY_FILE on startup
# or edit it live in the console (Policy page). JSON inline via DESCLES_POLICY.
defaults:
tools:
shell.rm: deny # nobody deletes files through an agent
service.delete: deny
require_approval:
- github.create_issue # always ask a human first
budget:
daily_usd: 10 # default daily spend cap per agent
groups: # group-wide rules (group = your Agents page
backend: # groups: team or person containers)
tools:
shell.kubectl.delete: require_approval
shell.kubectl.rollout: require_approval
budget:
daily_usd: 25
agents: # per-agent overrides — highest precedence
backend-deploy-bot: # (the agent id from your console)
tools:
deploy_to_prod: allow # explicit allow overrides the group rule
budget:
daily_usd: 50Precedence: agent rules > the agent’s group rules > defaults. An explicit allow at a higher layer overrides a deny/approval below it; within one layer, deny beats approval. Unlisted tools are allowed.
Argument-scoped rules (arg_tools)
Rules inside arg_tools match a tool call’s arguments, not just its name — the finest grain wins. They are evaluated before the plain tools rules of the same layer, and they inherit the same layer precedence (agent > group > defaults) and allow-override semantics. Each entry lists a tool, glob patterns per argument key (path, command, query, url, … — any key the runtime sends), and a decision:
defaults:
tools:
read_file: allow # plain rule: reading is fine…
arg_tools:
- tool: read_file # …but these paths are never readable
args:
path: ["**/.env", "**/secrets/**"]
decision: deny
- tool: terminal
args:
command: ["rm -rf*"] # destructive shells always ask a human
decision: require_approval
agents:
deploy-bot:
arg_tools:
- tool: terminal # a finer allow at the top layer wins
args:
command: ["git push*"]
decision: allowMatching: * matches any run of characters; a leading **/ matches any directory depth (**/.env also matches a bare .env). A rule matches when every listed argument key matches at least one of its globs; if the argument is missing or structured, its compact JSON form is matched. Among several matching argument rules, deny > require_approval > allow.
Caveat: argument matching on shell command strings is best-effort — rmdir, cd x && rm *, aliases and escapes all dodge a string glob, so treat a command deny as a speed bump, not a wall. Denies on structured keys like path / url are reliable; for a hard execution boundary use a sandbox or an action gateway.
Per-tool status labels
The console’s tool inventory (Policy page) labels each declared or used tool with the state of its effective defaults-layer rules:
- allowed — no rule (or an explicit allow) applies; calls pass unless a lower/higher layer says otherwise.
- approval — a plain
require_approvalrule holds every call for a human. - denied — a plain deny rule blocks every call; the runtime never sees it.
- partial — argument-scoped (
arg_tools) rules refine the tool: the plain disposition may be allow/approval but specific argument globs (e.g.read_fileallow +**/.envdeny) decide per call.
Status is computed from the defaults layer of the effective policy; per-agent overrides in agents can still change the outcome for a specific key. Skills declared by your runtime are just tools with skill names — they get the same status treatment as any other tool.
Tool names are runtime-specific
Rules match the tool names your agent actually declares — Descles does not invent names. If your agent’s tools are terminal, execute_code, web_search (e.g. a Hermes-style runtime), a rule for shell.rm governs nothing. Starter presets for common runtimes:
# Claude Code / Anthropic-style tools
defaults:
tools:
Bash: require_approval # name is exactly what the tool declares
Read: allow
Edit: require_approval
Write: deny
WebFetch: allow
require_approval:
- Bash
- Edit
# Hermes-style / Codex-style tools
defaults:
tools:
terminal: require_approval
execute_code: deny
web_search: allow
read_file: allow
patch: require_approvalMatching is exact after normalizing . and _ as the same separator (a rule for shell.rm also matches a tool named shell_rm). There is no fuzzy matching — an unknown spelling simply does not match, so a typo’d rule is silently inert. Check the Now in effect card after applying to confirm the rules you intended are live.
DESCLES_POLICY_FILE.Security model
- Identity — every data-plane key is bound to an agent (a member or a service); attribution comes from the key, never from client headers.
- Tenant isolation — your org sees only its own agents, groups, traces, budgets and approvals; platform views (signups, audit chain) stay with Descles.
- Tool policy — policy can strip a denied tool call from the model response or replace it with a pending-approval placeholder before the runtime ever executes it.
- Audit chain — governance events append to a signed hash chain; tampering breaks verification.
Limits & roadmap
| Available today | Chat Completions, Responses, Anthropic Messages; streaming tool-call enforcement (deny / human approval / resume) across all three SSE protocols; quota; budgets; hosted + per-request BYOK; hash-chained audit. |
|---|---|
| Planned | Slack approvals, broader native provider endpoints, hosted MCP / action-execution gateways (today the test launch delivers the Model Gateway: streaming tool intent governance, BYOK, budgets, traces), and content guardrails. |
Complaints, suggestions, or a roadmap vote? Write to outreach@descles.com.