{"slug": "how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors", "title": "How to Attribute AI Gateway Spend by Service with Stateful Actors", "summary": "A developer published Attribution Wheel, an open-source TypeScript sample that attributes AI gateway spend to individual services, environments, and tenants by attaching metadata to each metered request via an x-ltg-metadata header. Running on Telnyx Edge Compute, it uses one Stateful Actor per gateway token group to maintain a per-service daily spend ledger with idempotent upserts and a resumable sweep cursor, and flags anomalies using a seven-day baseline with thresholds of a 40-percentage-point share increase or 2.0x mean daily spend. The sample includes a demo mode for testing baseline, spike, and recovery flows without generating inference traffic.", "body_md": "An AI gateway invoice can tell you the total. It does not automatically tell you which application, environment, or tenant caused a sudden increase.\n\nThat 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.\n\n[Attribution Wheel](https://github.com/team-telnyx/telnyx-code-examples/tree/main/attribution-wheel) is an open-source TypeScript sample that treats spend attribution as a durable workflow:\n\nThe 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.\n\nAttribution 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.\n\nAttribution Wheel puts the dimensions on the request itself through the `x-ltg-metadata` header:\n\n```\nmetadata = {\n    \"service\": \"support\",\n    \"env\": \"prod\",\n    \"tenant\": \"example-health\"\n}\n\nheaders = {\n    \"Authorization\": f\"Bearer {service_token_key}\",\n    \"Content-Type\": \"application/json\",\n    \"x-ltg-metadata\": json.dumps(metadata),\n}\n```\n\nThe sample then requests spend grouped by `metadata:service`. It can also build report views grouped by `metadata:tenant` and `metadata:env`.\n\nThis makes the attribution dimensions part of the metered request rather than a separate reconciliation pipeline.\n\nThe sample combines four pieces:\n\n`bootstrap()` creates one token group and separate keys for the `support`, `billing`, and `docs` services.`AttributionWheel` actor owns the schedule, sweep cursor, token-group reference, and masked key references.`spend_daily` and `spend_dims`.\nThe actor is selected by name:\n\n``` js\nconst id = env.WHEELS.idFromName(group);\nconst wheel = env.WHEELS.get(id);\n```\n\nUsing the token-group name as the actor identity gives each group an independent state machine and ledger workflow.\n\nThe central SQL table uses `(date, service)` as its primary key:\n\n```\nCREATE TABLE spend_daily (\n  date TEXT NOT NULL,\n  service TEXT NOT NULL,\n  requests INTEGER NOT NULL DEFAULT 0,\n  input_tokens INTEGER NOT NULL DEFAULT 0,\n  output_tokens INTEGER NOT NULL DEFAULT 0,\n  spend REAL NOT NULL DEFAULT 0,\n  reserved_spend REAL NOT NULL DEFAULT 0,\n  unknown_requests INTEGER NOT NULL DEFAULT 0,\n  baseline_ratio REAL NOT NULL DEFAULT 0,\n  breach INTEGER NOT NULL DEFAULT 0,\n  source TEXT NOT NULL DEFAULT 'gateway',\n  note TEXT,\n  PRIMARY KEY (date, service)\n);\n```\n\nWrites use `ON CONFLICT(date, service) DO UPDATE`. Reprocessing a service for the same day therefore updates its row instead of inserting another copy.\n\nThe 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.\n\nThat distinction matters: database idempotency and notification delivery guarantees are related, but they are not the same thing.\n\nGlobal 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.\n\nAttribution Wheel calculates a seven-day history for each service and uses two checks:\n\nThe defaults are a 40-percentage-point share increase or `2.0x` mean daily spend.\n\nFor example, if support normally represents 20% of requests and reaches 62%, the increase is 42 percentage points. That crosses the default share threshold.\n\nThe 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.\n\n`bootstrap()` schedules the first run. After each scheduled sweep, `nightly()` schedules the next run for 23:00 UTC:\n\n```\nasync nightly(): Promise<void> {\n  try {\n    await this.sweep(shiftDate(todayUtc(), -1));\n  } finally {\n    await this.schedule(this.secondsUntil2300(), \"nightly\");\n  }\n}\n```\n\nThe 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.\n\nEach sweep:\n\nRequests that cannot be attributed to a known metadata value are surfaced as unknown rather than silently assigned to another service.\n\nClone the sample and install its dependencies:\n\n```\ngit clone https://github.com/team-telnyx/telnyx-code-examples.git\ncd telnyx-code-examples/attribution-wheel\nnpm install\ncp .env.example .env\n```\n\nKeep demo mode enabled:\n\n```\nTELNYX_API_KEY=your_telnyx_api_key_here\nATTRIBUTION_DEMO_MODE=true\nATTRIBUTION_ONCALL_E164=<ON_CALL_E164>\nATTRIBUTION_DEMO_NUMBER=<TELNYX_FROM_E164>\nATTRIBUTION_ALERT_SHARE_PCT=40\nATTRIBUTION_SPIKE_MULTIPLE=2.0\nAI_GATEWAY_MODEL=Kimi-K3\n```\n\nDeploy the Edge project and bootstrap the actor:\n\n```\ntelnyx-edge auth api-key set \"$TELNYX_API_KEY\"\ntelnyx-edge types\ntelnyx-edge ship\n\ncurl -s -X POST \"$BASE/rpc/bootstrap?group=main\" -d '[]'\n```\n\nSeed seven days of synthetic baseline data, then simulate a spike and recovery:\n\n```\ncurl -s -X POST \"$BASE/rpc/seed?group=main\" \\\n  -d '[7]'\n\ncurl -s -X POST \"$BASE/rpc/simulate?group=main\" \\\n  -d '[\"2026-11-10\", \"spike\"]'\ncurl -s -X POST \"$BASE/rpc/sweep?group=main\" \\\n  -d '[\"2026-11-10\"]'\n\ncurl -s -X POST \"$BASE/rpc/simulate?group=main\" \\\n  -d '[\"2026-11-11\", \"recovery\"]'\ncurl -s -X POST \"$BASE/rpc/sweep?group=main\" \\\n  -d '[\"2026-11-11\"]'\n```\n\nIn 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.\n\nFor a live deployment:\n\n`ATTRIBUTION_DEMO_MODE=false`.` x-ltg-metadata` values.\nMetadata 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.\n\nThe repository includes a smoke test for the actor surface, spend-row normalization, and dashboard rendering:\n\n```\nnpm test\n```\n\nThe implementation, setup guide, API reference, demo generator, and dashboard are available in the open-source repository:\n\n[Explore the Attribution Wheel sample on GitHub](https://github.com/team-telnyx/telnyx-code-examples/tree/main/attribution-wheel)\n\nAttribution 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.", "url": "https://wpnews.pro/news/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors", "canonical_source": "https://dev.to/sonam_gupta_dba00ec9473e5/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors-cb7", "published_at": "2026-11-12 23:44:15+00:00", "updated_at": "2026-11-12 23:49:26.840388+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools", "mlops", "developer-tools"], "entities": ["Attribution Wheel", "Telnyx", "Telnyx Edge Compute", "Telnyx AI Gateway", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors", "markdown": "https://wpnews.pro/news/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors.md", "text": "https://wpnews.pro/news/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors.txt", "jsonld": "https://wpnews.pro/news/how-to-attribute-ai-gateway-spend-by-service-with-stateful-actors.jsonld"}}