record
Attribute a completed provider call, or a batch of them, that had no budget reservation
record reports one caller-made provider call that has already completed and for which no reservation was taken — a call made before the guardrail ran, an offline replay, a local model at $0. It never touches the budget reservation plane: nothing is reserved, nothing is settled. Costs are computed server-side from the quantities you report.
Do not record a call that budget confirm already settled; confirm records attribution itself under the same call id.
solwyn record --model <name> --provider <provider> [--call-id <uuid>] [usage and attribution flags...]
solwyn record --batch <file>|-| Option | Type | Default | Purpose |
|---|---|---|---|
--model | string | required | Model that served the call. |
--provider | provider enum | required | Provider that served the call. |
--call-id | canonical lowercase UUID | generated | Your identifier for the logical call. Reuse it when retrying the same recording. |
--status | success | error | success | How the call ended. |
--latency-ms | float ≥ 0 | 0 | End-to-end latency. |
--modality | text | image | audio | video | embedding | text | The call's modality. |
--provider-region, --service-tier | string, enum | none | Serving region and tier, when relevant. |
--agent-run-id, --parent-run-id, --run-name | string | SOLWYN_AGENT_RUN_ID for the run id | Run attribution. See runs. |
--tag | key=value, repeatable | none | Attribution tags; overrides SOLWYN_TAGS. |
--is-estimated | boolean | false | Mark the token breakdown as estimated rather than provider-reported. |
--batch | path or - | none | A JSON array of event objects. Excludes every other flag. |
The thirteen token counters and seven media quantities are the same as budget confirm takes.
| Contract | |
|---|---|
| Auth | project key with full scope (a JWT is rejected) |
| Exit codes | 0 recorded or call-id replay · 1 rejection, collision, or API error · 2 usage · 3 auth |
| JSON schema | solwyn.record.v1 |
$ solwyn record --model gpt-4o --provider openai --input-tokens 900 --output-tokens 210 --tag env=prod
Field Value
Status ingested
Ingested 1
Rejected 0
Duplicates 0
Call IDs 12345678-1234-1234-1234-123456789abcOutcomes
Status tells you what happened, and the exit code follows it:
| Status | Meaning | Exit |
|---|---|---|
ingested | Recorded. | 0 |
deduplicated | The same call id was already recorded — a clean idempotent replay. | 0 |
rejected | The API refused the event; see the rejection code. | 1 |
failed | A partial or ambiguous write. | 1 |
not_recorded | Recognized capacity rejection with every attempt proven nonaccepting and no earlier completed chunk. | 1 |
Rejection codes are explained inline: unknown_model (use the exact model name), unknown_service_tier, invalid_tags, tag_cardinality_exceeded, unsupported_modality. A legacy_key duplicate — a collision on an older identity rather than the call id — is reported as failed with a warning, because it cannot prove the logical call is durable.
An API 400 or 422 exits 2, not 1, because it means the event you built is malformed. When a write fails partway, the CLI still prints the payload with the call ids it knows, then exits with the error, so a retry can reuse those ids instead of minting new ones.
Batch recording
$ solwyn record --batch events.json
Recorded batch progress: 1000/2400 events processed.
Recorded batch progress: 2000/2400 events processed.
Recorded batch progress: 2400/2400 events processed.The file is a JSON array of event objects using the wire field names (model, provider, input_tokens, tags, and so on). The CLI submits it in chunks of 1000, fills in a fresh SDK instance id for the whole invocation and a monotonically increasing timestamp per event, and applies SOLWYN_AGENT_RUN_ID and SOLWYN_TAGS to events that do not set their own. Do not supply cost_usd or lease_id. Any other flag alongside --batch is a usage error.
Identities, capacity and timing
Persist call_id before sending. Core deduplicates (project_id, call_id, attempt_index) while retained; this acknowledges a no-op and never updates an existing event. Batch events can preserve observed attempt indices. Generated IDs cannot recover a wholly lost response. Retain original call_ids, indices and known dispositions after partial or uncertain writes.
GET/ingest 503 retries use the shared bounded Retry-After allowance; that retry policy alone does not prove absence. Only the recognized ingest rejection rule establishes not_recorded. A later recognized rejection cannot erase earlier uncertainty. Do not mint new identities to force acknowledgement.
Budget and quota periods use server receive time; analytics use the stored event timestamp, within Core's clock and retention bounds. A backdated batch counts when received and appears on its original analytics day. It cannot bypass current-period limits.
Related
budget— the check → confirm loop for spend you can reserve firstruns— the run a recorded call is attributed totags— the keys and values you have recorded- Agent integration — where
recordfits in an agent's guardrail loop