SOLWYN

Meter un-instrumented calls

Enforce and attribute LLM spend the Solwyn SDK does not see — a curl, a shell script, another language, or a local model

The Solwyn SDK wraps a Python client and handles enforcement and attribution automatically. When a call does not go through that wrapper, the CLI gives you the same two guarantees in three commands: reserve budget before the call, settle the reservation with what the call actually used, and, for calls that already happened, record them after the fact.

This page is the walkthrough. Agent integration covers the same loop for coding agents that call it as MCP tools.

The one rule

Never run this loop around a call the SDK already wraps. The SDK reserves and settles on its own; wrapping it again holds the budget twice and can make a project deny itself. Use the CLI only for spend the SDK does not see.

What you need

  • A project key with full scope in SOLWYN_API_KEY. Reserving and settling are writes, so a read-only key exits 3 at the first check. See Key scopes.
  • The project ID, as --project or SOLWYN_PROJECT_ID.
  • jq, or any JSON parser, to read the reservation ID out of the check.

Reserve, spend, settle

Set a run ID once per job so every call in it is grouped, then run the three steps around each provider call.

Terminal
export SOLWYN_AGENT_RUN_ID="nightly-$(date +%Y%m%d)"
export SOLWYN_TAGS="env=prod,team=research"          # optional attribution tags
CALL_ID=$(uuidgen | tr A-F a-f)                     # one lowercase UUID per logical call

# 1. Reserve. A denied check exits 4, so the script stops here.
CHECK=$(solwyn --json budget check \
  --provider openai --model gpt-4o --estimated-input-tokens 1200) || exit $?
RID=$(printf '%s' "$CHECK" | jq -r '.reservation_id // empty')

# 2. Spend. Make the provider call yourself, with your own provider key.
RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"..."}]}')
IN=$(printf '%s' "$RESPONSE" | jq '.usage.prompt_tokens')
OUT=$(printf '%s' "$RESPONSE" | jq '.usage.completion_tokens')

# 3. Settle with the real token counts. Skip when the check returned no reservation.
if [ -n "$RID" ]; then
  solwyn --json budget confirm \
    --reservation-id "$RID" --call-id "$CALL_ID" \
    --provider openai --model gpt-4o \
    --input-tokens "$IN" --output-tokens "$OUT"
fi

Three details matter:

  • A denied check is exit 4, not an error. Treat it as "do not spend". In JSON the payload says allowed: false and names the budget that denied it.
  • A null reservation means fail-open. If the check returns allowed: true with reservation_id: null, Solwyn Cloud chose not to hold budget. There is nothing to settle, so skip confirm.
  • confirm records attribution for you. It uses the same call ID, so do not also record that call.

Calls that already happened

A call that ran with no reservation, such as a local model at zero cost, an offline replay, or a script written before the guardrail existed, is reported afterwards with record. It never touches the reservation plane; Solwyn Cloud prices it from the counts you report.

Terminal
solwyn record --provider ollama --model llama3 --input-tokens 120 --output-tokens 30
solwyn --json record --batch events.json      # a JSON array of events, chunked for you

Generate and persist a canonical lowercase call_id before sending. Core deduplicates (project_id, call_id, attempt_index) while retained; a duplicate is an acknowledged no-op, not a correction. Reuse identities when retrying attribution. A generated ID per invocation cannot protect a wholly lost reply, and a legacy_key collision does not prove call-ID durability. See record outcomes.

Beyond text tokens

Image, video, audio, and speech calls are priced by unit rather than by token. Pass the billed quantities to budget check so the call is priced and deniable before it runs, and to budget confirm so it settles at its real cost:

Call typeFlags on check and confirm
Image generation--image-count, --resolution, --quality
Video or audio--video-seconds, --audio-seconds, --generation-count
Speech synthesis--input-characters (a count, never the text)
Image-token models--image-input-tokens, --image-output-tokens on confirm

The full flag list, batch mode, and fallback declarations are on the budget reference.

Many calls at once

The standalone CLI budget check --batch validates a JSON array and submits legacy chunks of at most 32, preserving item order. Each allowed item has its own reservation, and any denial exits 4; this is not an atomic all-or-nothing batch. Failed chunks can have unknown effects, and there is no CLI journal recovery ID. MCP budget_check_batch instead uses a durable journal with a caller-retained UUIDv7 and GET-only recovery. See bounded MCP work.

Timing and recovery

Budget and quota periods use server receive time, while analytics use the stored event timestamp. A backdated recording counts when received and can appear on its original analytics day; backdating cannot bypass today's limits.

Settlement and recording are separate operations. Keep the original provider observations, stable call/attempt identities and the first exact returned recording_retry. After known settlement, retry only attribution; do not reserve again for completed work. For unit-priced calls, confirm observed media and actual served provider/model, keeping null as unobservable and zero as observed zero. Media settlement recovery covers lost replies and receipt expiry; Scripting distinguishes recognized unaccepted requests from unknown outcomes.

See also

On this page