Verify a statement. Every monthly statement from the console is signed. Drop the JSON file here, and its CSV if you have it, to check that both came from UplinkSwitch and have not been changed. The check runs in this browser; nothing is uploaded.

Public key: GET https://api.uplinkswitch.com/v1/statements/public-key

Check it in your own code

Node 20+
import { readFileSync } from 'node:fs';

const doc = JSON.parse(readFileSync('uplinkswitch-statement-2026-10.json', 'utf8'));
const { jwk } = await (await fetch('https://api.uplinkswitch.com/v1/statements/public-key')).json();
const key = await crypto.subtle.importKey('jwk', { kty: jwk.kty, crv: jwk.crv, x: jwk.x },
  { name: 'Ed25519' }, false, ['verify']);
const ok = await crypto.subtle.verify({ name: 'Ed25519' }, key,
  Buffer.from(doc.signature, 'base64url'), new TextEncoder().encode(doc.payload));
console.log(ok ? 'valid' : 'NOT valid');
Python · cryptography
import base64, json, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

doc = json.load(open("uplinkswitch-statement-2026-10.json"))
jwk = json.load(urllib.request.urlopen("https://api.uplinkswitch.com/v1/statements/public-key"))["jwk"]
b64 = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
key = Ed25519PublicKey.from_public_bytes(b64(jwk["x"]))
key.verify(b64(doc["signature"]), doc["payload"].encode())  # raises if not valid
print("valid")

Check a response receipt

What a statement is

Each month has a statement under Statements on the console's billing page. It downloads as a JSON file named uplinkswitch-statement-YYYY-MM.json, next to a CSV with one row per request: request_id, time_utc, key_id, model, status, outcome, input_tokens, cache_read_tokens, cache_write_tokens, output_tokens, charged_usd, list_cost_usd, saved_usd.

Dollar amounts are exact decimals with six places, worked out from whole micro-dollars. saved_usd is the list cost minus what you were charged, over the requests whose model has a list price.

A month is final one day after it ends. Until then the statement says final: false and can still change as the last requests are logged. From then on, the final statement is stored and served exactly as stored, so downloading it again gives the same file.

org
Your organization id and name.
period
The month, its start and end in UTC, and final: true once the month is closed.
totals
Requests; input, cache read, cache write and output tokens; charged_usd, list_cost_usd and saved_usd.
by_model
The same totals for each model you used.
by_key
Requests and charge for each API key, with its name.
payments
Each top-up recorded that month: credited and refunded.
usage_csv
The CSV column names, its row count and the SHA-256 of the whole file.

How the signature works

The file holds payload, the statement as one exact JSON string; signature, an Ed25519 signature over the UTF-8 bytes of that string, in base64url; and key_id, the RFC 7638 thumbprint of the public key that checks it. It also holds statement, a readable copy of the same data. Only payload is signed, so trust its figures if the two ever differ.

The payload carries the SHA-256 of the month's CSV. One signature covers both files: change one cell in the CSV and its hash no longer matches. The CSV lists requests oldest first, ties by request id, so the same month always gives the same bytes.

The checker above fetches the public key, compares its key_id with the file's, checks the signature with the browser's Web Crypto API, compares the readable copy with the payload, and hashes your CSV. Fetching the key is the only request it makes.

A valid signature means the holder of the UplinkSwitch signing key produced this payload and nothing in it has changed since. The key is public and needs no account, so your finance team or auditor can check a statement without access to the console.

Response receipts

A non-streamed answer comes with an x-uplink-receipt header: the base64url payload, a dot, then the signature. For any request, streamed or not, GET /v1/requests/:id/receipt with any key of your organization returns a signed receipt as JSON. A request reaches the log within about a minute of finishing.

Receipts are signed with the same key as statements, over the UTF-8 bytes of the payload. Your own users can check one here without an account.

request_id
The request, as in your usage log.
model
The model that answered, as you named it, after any alias.
billed_tokens
Input, cache read, cache write and output tokens billed.
charge_usd
What the request cost.
refund
Why the answer was not billed, when it was not: the model refused, or the answer did not match the output format the request asked for.
request_sha256, response_sha256
SHA-256 of the request body as sent and of the response body as relayed.