User guide

Operating the gateway once it is running: how callers choose a model, what policy controls, how verdicts and approvals work, and where the money goes. If you have not installed it yet, start with the installation guide.

Vocabulary #

TermMeans
WorkspaceA tenant. Maps to exactly one upstream project; credentials are never multiplexed across tenants
PrincipalWho is calling — a person, an application, an agent or a CI job
Virtual keyWhat an application actually holds. Scoped to a workspace and principal, with a TTL. Never the upstream credential
PolicyWhat a workspace may do: allowed models, feature toggles, tool gates, baseline, budget posture. Versioned; every PUT bumps the version
CatalogThe models you configured, with tiers, capabilities and your contract rates
Baseline modelThe model savings are measured against. A savings number with no baseline is marketing
VerdictThe verification outcome for one response: pass, flag, fail or skipped
RecordOne metadata-only row per inference, behind the live feed and the audit view

Choosing a model #

Callers put one of four things in the model field. The gateway does not guess how hard a request is — that was measured and it did not work, so the decision stays where the knowledge is, at the call site.

Ask forYou getUse it when
iag/fastCheapest allowed small-tier modelClassification, extraction, formatting, short factual answers, anything an agent does in a loop
iag/qualityCheapest allowed large-tier modelWork you would not want retried: long reasoning, code, anything customer-facing
iag/autoSmall first, with bounded escalation when verification is unhappyMixed traffic where you would rather pay a little more than hand-classify
a catalog idExactly that modelYou have a reason. The record shows it was pinned

Whatever the caller asks for, the response says what actually ran and why:

x-iag-model-routed: openai/gpt-oss-20b
x-iag-route-reason: alias iag/fast → cheapest allowed small-tier model
                    (excluded, upstream unavailable: local/qwen3-4b)

Policy #

Policy is per workspace and versioned. The cache key includes the policy version, so publishing a policy cannot serve an answer that an older policy allowed.

PUT /admin/v1/policy/ws-pilot
{
  "allowed_models": ["openai/gpt-oss-20b", "openai/gpt-oss-120b"],
  "features": {"routing": true, "cache": true, "trim": true,
               "verify": true, "tool_gating": true},
  "tool_gates": {"lookup_order": "read", "issue_refund": "financial"},
  "tool_memo":  {"lookup_order": {"ttl": "10m", "scope": "principal"}},
  "baseline_model": "openai/gpt-oss-120b",
  "budget_posture": "fail-closed"
}

A request naming a model outside allowed_models is refused at the gate, before it costs anything. The allowlist is intersected with the catalog, so a policy cannot introduce a model the operator did not configure.

Feature toggles #

ToggleOff means
routingAliases resolve to the baseline model instead of cascading. The reason still says so
cacheEvery request reaches the upstream. Useful when you are measuring something
trimNo context optimisation at all. Often the right setting — see why
verifyVerdicts come back skipped rather than fabricated as passes
tool_gatingTool calls pass through unheld. Turning it on buffers streamed tool responses so held calls can be stripped

Budgets #

PUT /admin/v1/budgets
{"workspace":"ws-pilot","principal":"alice","period":"monthly","cap_usd":250}

Caps are per workspace, or per principal within one. budget_posture decides what happens when the budget cannot be read: fail-closed refuses the request, fail-open serves it and reconciles afterwards. A breach returns a structured error naming the budget and what to do about it — never a silently truncated answer.

Verdicts and approvals #

Reading a verdict #

Verification is a set of named structural checks, not a truth oracle. It catches the failures that have a shape: an empty completion, a response truncated by the token limit, invalid JSON where the request demanded JSON, a refusal template, hedging density, repetition loops, a language mismatch, a tool call that violates its schema or a business rule.

VerdictMeansWhat the gateway does
passNo signals, confidence at or above 0.8Delivers it
flagSoft signals — hedging, repetition, a language mismatchDelivers it and records why
failHard signals — invalid JSON on a JSON contract, a schema violation on a gated toolHolds the tool call. Content is reported, never rewritten
skippedVerification off for this workspaceSays so, rather than reporting a pass

An optional judge can run as a second layer through your own small tier, spend-bounded per check. It is off by default, because it costs money on every request and the structural checks catch the shaped failures for free.

Tool gates #

A tool is classified by what it does, and the class decides whether a human sees it first. Read-class calls flow. Financial and external-communication classes are held.

"tool_gates": {
  "lookup_order":  "read",              // flows
  "send_email":    "external-comms",    // held for approval
  "issue_refund":  "financial"          // held for approval
}

A held call is stripped from the response before the client sees it, and the caller re-requests after the approval lands. That is deliberate: handing a client a tool call and then revoking it is not something the client can act on.

With tool gating on, a response that cannot be parsed at all is refused rather than delivered. A body whose tool calls cannot be inspected is a body whose tool calls cannot be gated.

The approval inbox #

curl -s -H "$A" "$B/approvals?workspace=ws-pilot&status=pending" | jq .
curl -s -H "$A" -X POST "$B/approvals/$ID/approve"
curl -s -H "$A" -X POST "$B/approvals/$ID/deny" -d '{"reason":"customer not verified"}'

Every decision is persisted with the actor, the reason and the time, and written into a hash-chained evidence ledger. The point is not the queue; it is that six months later you can reconstruct who allowed what, and prove the record has not been edited since.

Spending less #

In the order that actually matters, measured rather than assumed.

Keep the prefix stable #

This is the single biggest lever, and it is mostly about what you stop doing. Providers cache prompt prefixes; a stable system prompt comes back almost entirely from cache on every call after the first. One volatile byte at the front destroys that.

DoDo not
Keep the system prompt and tool schemas byte-identical between callsPut a timestamp, request id, nonce or trace id anywhere in the prefix
Let the gateway canonicalise: sorted keys, compact JSON, volatile fields moved out of the prefixRe-serialise tool schemas per request with unstable key order
Put per-request values in the user turn, at the endInterpolate the current date into the system prompt "for context"
Watch the prefix hit rate tileAssume it is working

The gateway prices the provider's cached prompt tokens at your contract's cached rate when you have configured one, and reports the share on every response and on the overview.

Tool-call memoisation #

Agent loops re-issue the same read-only call across turns — the same lookup on turn three and again on turn eleven. The gateway never executes tools, but it sees both the call and its result in the conversation, so it can finish the round-trip itself.

Learning is passive and costs nothing: the client already paid for those executions. When a model proposes calls that are all memo hits, the gateway appends the remembered results and re-invokes, and the client gets the continuation. A mixed proposal is returned untouched, because the client has to execute the miss anyway.

GuardRule
Opt-inNothing is memoised unless the policy names the tool
Read-class onlyRefused at write time and again at runtime. A side-effecting call served from memory would be a phantom action
Scopeprincipal by default; workspace is a per-tool choice for shared reference data. Never across workspaces
TTL, in processA tool result is content, so the memo is bounded, in memory, and dies with the process
BoundedAt most three continuations per request. A model looping on a memoised call is a loop, not a saving
AuditableAn evidence row per served call, a count on the record, and x-iag-tool-memo on the wire

What it saves is the client's tool execution and one full round-trip of latency per repeat. It does not save upstream tokens, and the product does not claim that it does.

Admin API #

Prefix /admin/v1 on the admin listener, bearer authentication. Lists return {data, next}; errors return {error: {code, message}}.

EndpointPurpose
GET /overview?workspace=&window=24hRequests, spend, baseline, savings, tokens, cache hit rate, blocked, held, p50/p95 latency, plus an hourly series
GET /requests?workspace=&after=&model=&verdict=&cache=Live feed and audit, newest first, cursor-paged
GET /requests/{trace_id}One record with the full routing decision
GET /approvals · POST /approvals/{id}/approve · /denyThe inbox
GET /keys · POST /keys · DELETE /keys/{id}Virtual keys. The token is returned once
GET /policy/{ws} · PUT /policy/{ws}Allowlist, toggles, gates, baseline, posture. PUT bumps the version
GET /budgets · PUT /budgets · DELETE /budgets/{id}Caps
GET /catalogConfigured models with tier, rates, capabilities, context and which upstream serves them
GET /workspaces · POST /workspacesTenancy
GET /healthReadiness payload for the console header

The console #

Served at / on the admin listener, on the same origin as the API. Every number on screen comes from the endpoints above. There is no fixture mode and no demo data.

PageWhat it answers
OverviewWhat did this cost, what did it save against the baseline, how much came from cache
Live feedWhat is happening right now, filterable by model, verdict and cache outcome
PlaygroundThe console calling the inference surface with a minted key — the same path an application takes
ApprovalsWhat is waiting on a human, and what was decided
KeysWho holds what, and revocation
Policy & BudgetsWhat each workspace may do and spend
AuditOne request, end to end, including the routing decision record

Day to day #

WatchBecause
Prefix hit rateA sudden drop usually means something started interpolating a value into a system prompt
Records flagged rate: floorA model is being used that is not in your catalog, so its cost is a safety net rather than a price
x-iag-cost-basis: estimatedUsage blocks are not arriving; usually aborted streams
Verdict mixA rising flag share is a prompt regression long before it is a complaint
Held call ageAn approval queue nobody reads is a broken workflow, not a control
Route reasons mentioning exclusionsA declared upstream is down and traffic is quietly falling back