Skip to main content
If you’re building a SaaS product on top of LLMs, and you want to let your customers bring their own budget without giving them direct provider credentials, this pattern is for you. You become the upstream provider; the LangWatch AI Gateway becomes your provisioning + enforcement + audit layer.

The shape

Key properties:
  • Each end-user has their own VK. One per customer, or per seat, your call.
  • You set per-customer budgets. Enforced at the gateway. When a customer hits their cap, the gateway returns 402 budget_exceeded. Your code doesn’t need to track spend.
  • All audit + trace data lands in your LangWatch project. End-users never see LangWatch.
  • End-users can’t escape your policy. Policy rules, model allowlists, cache rules all attach to the VK.

Step 1: Model the tenancy

Decide your scope granularity: For this walkthrough, assume per-customer model. Note that principal budgets target real LangWatch user accounts, which your external customers are not; for reseller tenancy the per-customer unit is the virtual key itself.

Step 2: Configure the upstream ModelProvider once

Your LangWatch org owns a single ModelProvider row per upstream (OpenAI, Anthropic) at ORGANIZATION scope. End-users never see these. Configure them under Settings → Model Providers → Add Model Provider (Scope = Organization), then open each row’s Advanced (Gateway) tab and set the per-credential gateway caps: No separate “binding” entity to mint. The ModelProvider id is what your code references:

Step 3: Provision a VK with its cap when a customer signs up

One call mints the key AND its budget, atomically: the key can never exist for a moment without the cap you asked for. Server-side code (Node.js example):
The eligible-provider set is the union of every ModelProvider visible from the key’s scopes (the ones you configured in Step 2). Pin routing_policy_id plus routing_mode: "POLICY" if you run a custom ordering; otherwise leave routing at its default. In a signup webhook:

Step 4: Change the cap when the plan changes

The budget field on update upserts the key’s own cap, so plan changes are one call keyed by the VK id you stored (no budget id bookkeeping):
Pass budget: null to remove the cap entirely (the spend history stays). Changes propagate to the gateway within 30 s via the /changes feed, no restart, no customer impact.

Step 5: End-user makes a call

Your customer’s app calls the gateway directly with the VK you gave them:
The gateway:
  • Authenticates the VK.
  • Checks the customer’s budget.
  • Applies your modelsAllowed allowlist.
  • Dispatches to OpenAI using your provider credential.
  • Meters the cost against the customer’s budget.
  • Emits a LangWatch trace into your project, attributed to the key.

Step 6: Show the customer their spend and bill them

The read-back half of the loop is one REST call per key:
or from a shell:
The number comes from the same cost path the LangWatch dashboard reads, so what you invoice matches what you see in the UI. For the enforcement view (spend against the cap), list VK-scoped budgets:
spend_available: false in that output means spend could not be totalled server-side; hold billing until it recovers rather than invoicing a zero.

(a) Bill from your pricing table

You define a markup over what the gateway shows you spent, and your Stripe/billing layer reads the spend numbers above and invoices.

(b) Passthrough billing

For passthrough, your UI shows the same numbers you see in LangWatch + a per-customer markup or flat management fee.

Handling over-limit customers

When on_breach: BLOCK, the gateway returns 402 budget_exceeded. Your customer sees:
Your customer’s app should catch this and either:
  • Show an upgrade CTA in the end-user’s UI.
  • Fall back to a free-tier response (“you’ve hit your monthly cap; upgrade for unlimited”).
Because the response is standard OpenAI-compatible, your error handler only needs to switch on error.type.

Rotation & revocation

Customer resets their API key in your UI → you call langwatch.rotate(vkId), get a new secret, send it to the customer. The old secret keeps authenticating for a 24 h grace window, so a client you have not reached yet does not start failing mid-rollout. The gateway’s cache TTL (~60 s) is only how long the new secret takes to be accepted everywhere, not when the old one expires. Customer cancels their subscriptionlangwatch.revoke(vkId). The next request returns 403 virtual_key_revoked. If you want a grace period, schedule the revoke job for 24–48 h after cancellation instead.

Audit: who did what

Every write through your backend credential is audited with action, target, and before/after metadata. Scoped API keys attribute the write to the key’s owning user; legacy project keys carry no user, so their writes record the machine principal svc_<your_project_id>. Filter the audit log on target virtual_key to get a per-customer provisioning history, under /settings/audit-log in the LangWatch UI. If you need SIEM export: query ONLY the gateway-shape AuditLog rows with a read-only role scoped to that table, and ship the filtered result set, never a whole-database dump (which would carry unrelated, sensitive application data). For example, a daily job running COPY (SELECT id, "organizationId", "userId", action, "targetKind", "targetId", "createdAt" FROM "AuditLog" WHERE "organizationId" = '<your-org-id>' AND "targetKind" IN ('virtual_key', 'budget', 'model_provider', 'routing_policy', 'cache_rule') AND "createdAt" >= now() - interval '1 day') TO STDOUT WITH CSV into your SIEM’s ingestion path: an explicit column list pinned to your organization, leaving the before/after payloads out unless your SIEM genuinely needs them. There is no public REST audit-export endpoint in v1, see Audit log → Querying programmatically for the supported paths (UI CSV download, direct SQL).

Gotchas

  • Never let the customer see your backend API token. It has virtualKeys:create; they’d provision more VKs charged to you.
  • Rotate VKs when an end-user leaves your customer’s org. Otherwise the ex-user keeps spend access until the budget resets.
  • Name keys after your tenant id (customer-<id>) at creation time. The key’s name and id are your join keys for spend read-back and audit history; principal_user_id is only for real LangWatch user accounts, not your external customers.
  • Test the 402 path in your app before go-live. Many apps have unhandled exceptions on budget breach and crash the user’s flow.
  • Budgets scoped to virtual_key are the right level for per-customer enforcement, and the inline budget field on create/update manages exactly that budget for you.

Rate limits at your gateway level

If you want to throttle a specific customer without moving them to a different plan:
Gateway enforces at the VK level; customer sees 429 rate_limit_exceeded. Combine with a short-window budget (hour, minute) for finer control.

See also