Portal
Documentation: all sections

Delegated sub-keys

POST /v1/keys/delegate mints a narrower child of the calling API key. It's authenticated by the parent key itself, not a wallet session. The point is that an agent can hand a sub-agent a bounded credential mid-run, without a human in the loop.

curl https://api.buildsable.com/v1/keys/delegate \
-H "authorization: Bearer $SABLE_API_KEY" \
-H 'content-type: application/json' \
-d '{
  "name": "research-subagent",
  "spend_limit_usd": 0.50,
  "allowed_models": ["sable-llama-3.1-8b", "sandbox"],
  "expires_in_minutes": 15,
  "rate_limit_per_min": 10
}'

The plaintext key is returned exactly once.

The guarantee

A child is ≤ its parent on every axis, always. There is no request shape that widens a scope; the gateway clamps rather than validates, so an over-broad request produces a correctly-narrow key instead of an error.

FieldRule
spend_limit_usdThe tighter of the two
allowed_modelsIntersection with the parent's
expires_in_minutesThe earlier expiry of the two
rate_limit_per_minThe tighter of the two
privacy tierInherited; not settable by the child

Two behaviours are worth being explicit about, because the intuitive reading is the opposite:

The tier is inherited rather than accepted from the request because tier selects routing, and on the confidential tier, which hardware attests the call. A child picking its own would let it change where its traffic executes.

Spend rolls up

A parent's monthly cap counts its own usage plus every descendant's. Without that, delegation would be a trivial way around a budget: mint a child, spend through it, never touch your own counter.

So a $5 parent that delegates a $0.50 child still stops at $5 total across the whole tree.

And it isn't just the immediate parent: every ancestor's cap is enforced on each request. A child can never help exceed any budget above it, including by sibling fan-out, where ten $0.50 children under a $2 parent would otherwise add up to $5 of spend against a $2 budget. The tree stops at the tightest ancestor, whichever level it sits at.

Live children are bounded

A parent can hold a limited number of live (unexpired, unrevoked) children at once, default 32. Delegating past the cap is refused; let short-lived children expire, or revoke ones you no longer need. This keeps a runaway loop from minting keys faster than anyone could audit them.

Revocation cascades

Revoking a key revokes every key beneath it. A revoked parent whose children kept working would mean revocation didn't revoke anything an attacker bothered to delegate first.

Revoking a child leaves the parent untouched.

Depth is bounded

A chain is limited to 3 keys including the root: agent → sub-agent → tool. A fourth level returns 400. This keeps a compromised key from minting an unbounded chain that's tedious to audit and slow to revoke.

Suggested pattern

Give each task its own short-lived key:

  1. The orchestrator holds one long-lived key with the real budget.
  2. Per task, delegate a child scoped to just the models that task needs, capped at what the task should cost, expiring in minutes.
  3. Don't bother revoking; let it expire.

Worst case for a leaked or runaway sub-key is then bounded by construction rather than by noticing.