How to Attribute AI Gateway Spend by Service with Stateful Actors
An AI gateway invoice can tell you the total. It does not automatically tell you which application, environment, or tenant caused a sudden increase. That becomes a real operational problem when several services share th
An AI gateway invoice can tell you the total. It does not automatically tell you which application, environment, or tenant caused a sudden increase.
That becomes a real operational problem when several services share the same gateway. A support assistant, billing workflow, and documentation tool may all be healthy from an availability perspective while one of them quietly produces far more requests or spend than usual.
Attribution Wheel is an open-source TypeScript sample that treats spend attribution as a durable workflow:
- Add metadata to each AI Gateway inference request.
- Group the gateway's spend summary by that metadata.
- Store one daily ledger row per service.
- Compare each service with its own seven-day history.
- Send an SMS when a new anomaly appears.
The sample runs on Telnyx Edge Compute and uses one Stateful Actor per AI Gateway token group. A built-in demo mode lets you test the baseline, spike, and recovery flow without generating inference traffic or sending a real alert.
Why tag inference requests at the source?
Attribution gets harder when it is reconstructed later from application logs. Logs may use different identifiers, arrive late, or omit the information needed to connect a request with a service or tenant.
Attribution Wheel puts the dimensions on the request itself through the x-ltg-metadata header:
metadata = {
"service": "support",
"env": "prod",
"tenant": "example-health"
}
headers = {
"Authorization": f"Bearer {service_token_key}",
"Content-Type": "application/json",
"x-ltg-metadata": json.dumps(metadata),
}
The sample then requests spend grouped by metadata:service. It can also build report views grouped by metadata:tenant and metadata:env.
This makes the attribution dimensions part of the metered request rather than a separate reconciliation pipeline.
Architecture
The sample combines four pieces:
-
AI Gateway token groups and keys:
bootstrap()creates one token group and separate keys for thesupport,billing, anddocsservices. -
Stateful Actor: one
AttributionWheelactor owns the schedule, sweep cursor, token-group reference, and masked key references. -
SQL ledger: daily spend and dimensional summaries are stored in
spend_dailyandspend_dims. - Telnyx Messaging: a new breach can trigger an SMS to the configured on-call number.
The actor is selected by name:
const id = env.WHEELS.idFromName(group);
const wheel = env.WHEELS.get(id);
Using the token-group name as the actor identity gives each group an independent state machine and ledger workflow.
A daily ledger that can be safely updated
The central SQL table uses (date, service) as its primary key:
CREATE TABLE spend_daily (
date TEXT NOT NULL,
service TEXT NOT NULL,
requests INTEGER NOT NULL DEFAULT 0,
input_tokens INTEGER NOT NULL DEFAULT 0,
output_tokens INTEGER NOT NULL DEFAULT 0,
spend REAL NOT NULL DEFAULT 0,
reserved_spend REAL NOT NULL DEFAULT 0,
unknown_requests INTEGER NOT NULL DEFAULT 0,
baseline_ratio REAL NOT NULL DEFAULT 0,
breach INTEGER NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT 'gateway',
note TEXT,
PRIMARY KEY (date, service)
);
Writes use ON CONFLICT(date, service) DO UPDATE. Reprocessing a service for the same day therefore updates its row instead of inserting another copy.
The actor also checkpoints the last processed service in a cursor. If a sweep is interrupted, a later call for the same day can resume after that cursor. This protects the ledger from duplicate rows, but production alert delivery still deserves its own idempotency key or outbox if duplicate suppression must survive every possible failure between sending an SMS and recording that it was sent.
That distinction matters: database idempotency and notification delivery guarantees are related, but they are not the same thing.
Detecting a service-specific anomaly
Global spend thresholds are often too blunt. A large service may cross them during normal traffic while a smaller service can multiply its spend without reaching the same absolute number.
Attribution Wheel calculates a seven-day history for each service and uses two checks:
- The service's request share jumps by the configured number of percentage points over its historical share.
- The service's daily spend reaches the configured multiple of its mean daily spend.
The defaults are a 40-percentage-point share increase or 2.0x mean daily spend.
For example, if support normally represents 20% of requests and reaches 62%, the increase is 42 percentage points. That crosses the default share threshold.
The sample records the result as an anomaly, continued breach, recovery, or normal row. It sends an alert only when the code sees a new breach, rather than sending another message for every sweep while the same condition remains active.
Scheduled execution and recovery
bootstrap() schedules the first run. After each scheduled sweep, nightly() schedules the next run for 23:00 UTC:
async nightly(): Promise<void> {
try {
await this.sweep(shiftDate(todayUtc(), -1));
} finally {
await this.schedule(this.secondsUntil2300(), "nightly");
}
}
The scheduled handler and the manual sweep(day) RPC use the same code path. That makes the workflow easier to test and allows an operator to rerun a specific day deliberately.
Each sweep:
- Pulls spend grouped by service.
- Loads the previous seven days from SQL.
- Calculates each service's request share and spend change.
- Upserts the daily ledger row.
- Sends an SMS for a newly detected breach.
- Saves the cursor after each processed service.
- Pulls a usage snapshot for the report.
Requests that cannot be attributed to a known metadata value are surfaced as unknown rather than silently assigned to another service.
Run the demo without spending tokens
Clone the sample and install its dependencies:
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/attribution-wheel
npm install
cp .env.example .env
Keep demo mode enabled:
TELNYX_API_KEY=your_telnyx_api_key_here
ATTRIBUTION_DEMO_MODE=true
ATTRIBUTION_ONCALL_E164=<ON_CALL_E164>
ATTRIBUTION_DEMO_NUMBER=<TELNYX_FROM_E164>
ATTRIBUTION_ALERT_SHARE_PCT=40
ATTRIBUTION_SPIKE_MULTIPLE=2.0
AI_GATEWAY_MODEL=Kimi-K3
Deploy the Edge project and bootstrap the actor:
telnyx-edge auth api-key set "$TELNYX_API_KEY"
telnyx-edge types
telnyx-edge ship
curl -s -X POST "$BASE/rpc/bootstrap?group=main" -d '[]'
Seed seven days of synthetic baseline data, then simulate a spike and recovery:
curl -s -X POST "$BASE/rpc/seed?group=main" \
-d '[7]'
curl -s -X POST "$BASE/rpc/simulate?group=main" \
-d '["2026-11-10", "spike"]'
curl -s -X POST "$BASE/rpc/sweep?group=main" \
-d '["2026-11-10"]'
curl -s -X POST "$BASE/rpc/simulate?group=main" \
-d '["2026-11-11", "recovery"]'
curl -s -X POST "$BASE/rpc/sweep?group=main" \
-d '["2026-11-11"]'
In demo mode, alerts are logged instead of sent and the history is clearly marked as synthetic. The dashboard and report(day) RPC still show the complete attribution flow.
Moving from demo mode to real traffic
For a live deployment:
- Set
ATTRIBUTION_DEMO_MODE=false. - Configure placeholder values with real Telnyx numbers through secrets rather than committing them.
- Send each service's inference traffic with its own token key and
x-ltg-metadatavalues. - Confirm that the API key has the required AI Gateway scopes, including usage access.
- Choose thresholds based on real history instead of assuming the sample defaults fit every workload.
- Add an alert outbox or delivery-idempotency strategy if strict notification deduplication is required.
Metadata can contain tenant identifiers, so use internal opaque IDs rather than customer names or sensitive information. Establish retention and access policies for the attribution ledger as well.
Test the implementation
The repository includes a smoke test for the actor surface, spend-row normalization, and dashboard rendering:
npm test
The implementation, setup guide, API reference, demo generator, and dashboard are available in the open-source repository:
Explore the Attribution Wheel sample on GitHub
Attribution does not need to begin as a separate analytics platform. A useful first step is simply to put stable dimensions on each request, preserve the gateway's daily totals in an idempotent ledger, and compare every service with its own history.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.