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

# Consume Credits

> Deduct credits from a tenant's credit pool. Idempotent.

Deducts credits from a tenant's pool. Credits are deducted FIFO — base credits first, then add-on credits, earliest-expiry first.

Passing the same `idempotencyKey` twice returns the original result without double-deducting.

## Request Body

<ParamField body="tenantId" type="string" required>
  The external tenant ID from your application (e.g. `workspace_123`)
</ParamField>

<ParamField body="poolKey" type="string" required>
  The credit pool to deduct from (e.g. `api_calls`, `ai_tokens`). Must match a pool defined on the tenant's active plan.
</ParamField>

<ParamField body="amount" type="integer" required>
  Number of credits to consume. Must be a positive integer.
</ParamField>

<ParamField body="idempotencyKey" type="string" required>
  Unique key for this consumption event. Duplicate calls with the same key return the original result — no double deduction.
</ParamField>

<ParamField body="productSlug" type="string">
  Scope consumption to the tenant's active subscription for this product (e.g. `ats`). A tenant holds at most one active subscription per product, so this is the recommended way to disambiguate multi-product tenants. Without it, the most recently created active subscription is used.
</ParamField>

<ParamField body="subscriptionId" type="string">
  Scope consumption to an exact subscription ID. Takes effect together with `productSlug` if both are passed (they must refer to the same subscription).
</ParamField>

<ParamField body="metadata" type="object">
  Optional context stored on the event for auditing (e.g. `{ "endpoint": "/api/export", "userId": "u_123" }`).
</ParamField>

## Authentication

Requires a **secret key** (`Authorization: Bearer sk_live_...`) or **service key** (`x-service-key`).

## Response

<ResponseField name="data.result" type="string">
  Outcome: `allowed`, `warning`, or `blocked`.

  * `allowed` — credits deducted successfully
  * `warning` — credits deducted but balance is now below zero (soft limit pool)
  * `blocked` — insufficient credits on a hard limit pool; no deduction occurred
</ResponseField>

<ResponseField name="data.remaining" type="integer">
  Credits remaining in the pool after this deduction.
</ResponseField>

<ResponseField name="data.alreadyProcessed" type="boolean">
  `true` if this `idempotencyKey` was already processed. No deduction occurred on this call.
</ResponseField>

<ResponseField name="data.poolKey" type="string">
  The pool key that was targeted.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://app.crovver.com/api/public/credits/consume" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "tenantId": "workspace_123",
      "poolKey": "api_calls",
      "amount": 1,
      "idempotencyKey": "consume-1778423112701",
      "metadata": { "endpoint": "/api/export" }
    }'
  ```

  ```typescript Node.js theme={null}
  const res = await fetch("https://app.crovver.com/api/public/credits/consume", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_live_...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      tenantId: "workspace_123",
      poolKey: "api_calls",
      amount: 1,
      idempotencyKey: `consume-${Date.now()}`,
    }),
  });
  const { data } = await res.json();
  if (data.result === "blocked") {
    // hard limit hit — surface upgrade prompt
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Allowed theme={null}
  {
    "success": true,
    "data": {
      "result": "allowed",
      "remaining": 849,
      "alreadyProcessed": false,
      "poolKey": "api_calls"
    }
  }
  ```

  ```json 200 Blocked (hard limit) theme={null}
  {
    "success": true,
    "data": {
      "result": "blocked",
      "remaining": 0,
      "alreadyProcessed": false,
      "poolKey": "api_calls"
    }
  }
  ```

  ```json 200 Already processed theme={null}
  {
    "success": true,
    "data": {
      "result": "allowed",
      "remaining": 849,
      "alreadyProcessed": true,
      "poolKey": "api_calls"
    }
  }
  ```

  ```json 402 No active subscription theme={null}
  {
    "success": false,
    "data": null,
    "error": {
      "message": "No active subscription found for this tenant",
      "code": "NO_SUBSCRIPTION"
    }
  }
  ```
</ResponseExample>
