> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qlaud.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Per-user billing

> Mint a qlaud key for every end-user, cap their spend, invoice them through Stripe.

This guide walks through the headline qlaud workflow — minting a `qlk_live_…`
key per end-user of your app, enforcing a per-user spending cap, and
generating invoices from per-key usage.

## Mental model

| Stripe pattern               | qlaud equivalent                                          |
| ---------------------------- | --------------------------------------------------------- |
| `Customer`                   | A `qlk_live_…` key with `name: "user_<id>"`               |
| Subscription / metered usage | Per-key `usage_events` we record on every API call        |
| Spending limit               | `max_spend_usd` enforced on the key                       |
| Webhook on overage           | (coming soon) `cap_exceeded` webhook                      |
| Invoice                      | `GET /v1/usage` → fold into Stripe `InvoiceItem.create()` |

You hold one **master key** (`scope: 'admin'`) that lets you mint and revoke
per-user keys. Every other key is `scope: 'standard'` and can only be used
for inference.

## Architecture

```
                                                   ┌── /v1/messages ─→ Claude
your user ──HTTP──→ your backend ──qlk_live_user_42──→ qlaud ──┼── /v1/audio/* ─→ OpenAI
                                                   └── /v1/videos ──→ Sora 2

           Master key holder (you)
              ↓
        POST /v1/keys ──── mint per-user keys
        GET  /v1/usage ─── pull per-user spend
        DEL  /v1/keys/:id  revoke when user churns
```

## Step 1 — Mint your master key

In the [dashboard](https://qlaud.ai/keys), create a key with scope **Master
(admin)**. Store it as `QLAUD_MASTER_KEY` in your backend's secret manager.
Never expose this key to clients — it can mint other keys.

## Step 2 — Mint a per-user key on signup

When a user signs up to **your** app, mint a qlaud key for them with their
monthly cap.

<CodeGroup>
  ```typescript node theme={null}
  // pages/api/signup.ts (or wherever you handle signup)
  async function onUserSignup(user: { id: string; email: string }) {
    const r = await fetch('https://api.qlaud.ai/v1/keys', {
      method: 'POST',
      headers: {
        'x-api-key': process.env.QLAUD_MASTER_KEY!,
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        name: `user_${user.id}`,        // we surface this in /v1/usage
        max_spend_usd: 5,                // hard cap; gateway-enforced
      }),
    });
    const { id, secret } = await r.json();

    // Store with the user — `secret` is shown ONCE; we only keep a hash.
    await db.users.update(user.id, {
      qlaud_key_id: id,
      qlaud_key_secret: secret,
    });
  }
  ```

  ```python python theme={null}
  # users/views.py (or wherever you handle signup)
  import os, requests

  def on_user_signup(user):
      r = requests.post(
          "https://api.qlaud.ai/v1/keys",
          headers={"x-api-key": os.environ["QLAUD_MASTER_KEY"]},
          json={
              "name": f"user_{user.id}",
              "max_spend_usd": 5,
          },
      )
      body = r.json()
      user.qlaud_key_id = body["id"]
      user.qlaud_key_secret = body["secret"]   # shown ONCE
      user.save()
  ```

  ```bash curl theme={null}
  curl https://api.qlaud.ai/v1/keys \
    -H "x-api-key: $QLAUD_MASTER_KEY" \
    -H "content-type: application/json" \
    -d '{"name":"user_42","max_spend_usd":5}'

  # {
  #   "id": "0e2c3a91-...",
  #   "name": "user_42",
  #   "secret": "qlk_live_abc...wxyz",
  #   "prefix": "qlk_live_abcd…wxyz",
  #   "scope": "standard",
  #   "max_spend_usd": 5
  # }
  ```
</CodeGroup>

<Note>
  The `secret` is returned **once** at creation time. Save it immediately — we
  only store its SHA-256 hash. If you lose it, you must revoke the key and
  mint a new one.
</Note>

## Step 3 — Use the per-user key for inference

In your app's request flow, swap the master key for the user's key when
calling qlaud. **The cap is enforced gateway-side** — you don't need any
extra logic.

```typescript theme={null}
// When user_42 calls your /chat endpoint:
const userKey = await db.users.findById(user.id).qlaud_key_secret;

const r = await fetch('https://api.qlaud.ai/v1/messages', {
  method: 'POST',
  headers: {
    'x-api-key': userKey,
    'anthropic-version': '2023-06-01',
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    model: 'claude-sonnet-4-6',
    max_tokens: 1000,
    messages,
  }),
});

// If user_42 has hit their $5 cap, qlaud returns 402 with:
// {"error":{"type":"authentication_error",
//           "message":"this API key has reached its spending cap..."}}
```

## Step 4 — Bill at month-end

Pull per-user spend from qlaud and create Stripe invoice items.

<CodeGroup>
  ```python python theme={null}
  import os, requests, stripe
  stripe.api_key = os.environ["STRIPE_SECRET_KEY"]

  usage = requests.get(
      "https://api.qlaud.ai/v1/usage",
      headers={"x-api-key": os.environ["QLAUD_MASTER_KEY"]},
  ).json()

  for k in usage["by_key"]:
      user = db.users.find_by_qlaud_key_id(k["key_id"])
      if k["cost_micros"] == 0 or not user.stripe_customer_id:
          continue

      stripe.InvoiceItem.create(
          customer=user.stripe_customer_id,
          amount=int(k["cost_micros"] / 100),    # micro-dollars → cents
          currency="usd",
          description=f"AI usage — {k['request_count']} requests",
      )
  ```

  ```typescript node theme={null}
  import Stripe from 'stripe';
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

  const usage = await fetch('https://api.qlaud.ai/v1/usage', {
    headers: { 'x-api-key': process.env.QLAUD_MASTER_KEY! },
  }).then((r) => r.json());

  for (const k of usage.by_key) {
    const user = await db.users.findByQlaudKeyId(k.key_id);
    if (k.cost_micros === 0 || !user?.stripeCustomerId) continue;

    await stripe.invoiceItems.create({
      customer: user.stripeCustomerId,
      amount: Math.floor(k.cost_micros / 100),    // micro-dollars → cents
      currency: 'usd',
      description: `AI usage — ${k.request_count} requests`,
    });
  }
  ```
</CodeGroup>

## Optional — date-range billing

`/v1/usage` defaults to month-to-date. Pass `from_ms` and `to_ms` (UTC
milliseconds) to scope to any window:

```bash theme={null}
# Last 30 days
NOW=$(date +%s)000
THEN=$(( NOW - 30 * 86400 * 1000 ))
curl "https://api.qlaud.ai/v1/usage?from_ms=$THEN&to_ms=$NOW" \
  -H "x-api-key: $QLAUD_MASTER_KEY"
```

## Optional — drill down to one user

```bash theme={null}
curl "https://api.qlaud.ai/v1/keys/<key_id>/usage" \
  -H "x-api-key: $QLAUD_MASTER_KEY"

# Returns: { total_cost_micros, max_spend_micros, events: [...] }
# events = last 100 requests with model, status, latency_ms, cost
```

## Revoking a key

When a user churns or you need to roll a key:

```bash theme={null}
curl -X DELETE https://api.qlaud.ai/v1/keys/<key_id> \
  -H "x-api-key: $QLAUD_MASTER_KEY"
```

Revocation propagates to our cache immediately — the key stops working on
the next request, no waiting for TTL.

## What you didn't have to build

* Per-user usage tracking → `usage_events` table on our side
* Per-user spending caps → `max_spend_micros` column, KV-cached check
* Failed-payment handling for AI usage → `402` from qlaud, propagate to user
* Per-provider billing reconciliation → one wallet, one invoice from us
* Storing customer-facing AI prices → catalog already includes our 7% markup

## Coming soon

* **Webhooks** — `cap_exceeded`, `low_balance`, `key_revoked`. POST to your URL
  on event.
* **Per-key recharge** — `POST /v1/keys/:id/credit` to add credit on a single
  user-key without touching your master wallet (for "user paid you, push
  credit to their key" flows).
* **`@qlaud/sdk`** — Stripe-SDK-shaped Node + Python clients with
  `qlaud.keys.create()`, `qlaud.usage.list()`, etc.
