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

# Credits and budget

> Read the balance, the amount held by requests in flight, the period spend and the spend cap behind a workspace API key, before a request is refused for credits.

```http theme={"dark"}
GET https://api.runinfra.ai/v1/credits
```

This endpoint reads the workspace credit balance and budget for pay-as-you-go work. A coding plan can fund covered requests without spending that balance. Use [GET /v1/usage](/docs/api-reference/usage) for coding plan windows and policy; `/v1/credits` does not include a `coding_plan` field.

It is read-only and charges nothing. It is authenticated and rate-limited like every other endpoint, and the response is never served from an HTTP cache.

## Request

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl https://api.runinfra.ai/v1/credits \
    -H "Authorization: Bearer $RUNINFRA_GATEWAY_KEY"
  ```

  ```python Python theme={"dark"}
  import os
  import requests

  credits = requests.get(
      "https://api.runinfra.ai/v1/credits",
      headers={"Authorization": f"Bearer {os.environ['RUNINFRA_GATEWAY_KEY']}"},
      timeout=10,
  ).json()

  if credits["available_cents"] < 500:
      print("under $5.00 available, top up before the next run")
  ```

  ```typescript TypeScript theme={"dark"}
  const res = await fetch("https://api.runinfra.ai/v1/credits", {
    headers: { Authorization: `Bearer ${process.env.RUNINFRA_GATEWAY_KEY}` },
  });
  const credits = await res.json();

  if (credits.available_cents < 500) {
    console.log("under $5.00 available, top up before the next run");
  }
  ```
</CodeGroup>

The OpenAI SDKs have no credits method, so call the endpoint with your HTTP client. The same key you use for inference works here, as long as it is a workspace key (see [Who can read it](#who-can-read-it)).

## Response

```json theme={"dark"}
{
  "object": "credits",
  "workspace_id": "00000000-0000-4000-8000-000000000000",
  "balance_cents": 5476,
  "held_cents": 34,
  "available_cents": 5476,
  "currency": "usd",
  "period": {
    "start": "2026-08-10T00:00:00.000Z",
    "spent_cents": 1500
  },
  "spend_cap": {
    "limit_cents": 20000,
    "hard": false,
    "used_cents": 1620,
    "remaining_cents": 18380,
    "gates_inference": false
  },
  "plan_tier": "core",
  "as_of": "2026-08-27T10:41:12.318Z"
}
```

| Field | Type | Value |
| - | - | - |
| `object` | string | `credits`. |
| `workspace_id` | string | `workspace_id` identifies the workspace authenticated by the API key. A UUID. |
| `balance_cents` | integer | The stored ledger balance of the workspace, in US cents. Holds for requests in flight are already taken out of it. Can be negative when the workspace owes for usage that settled after the balance ran out. The same number a `402` reports as `error.current_balance_cents`. |
| `held_cents` | integer | Cents reserved by requests currently in flight. Informational: they are already inside `balance_cents`, so do not subtract them again. Each hold is refunded or recognised as spend when its request settles. |
| `available_cents` | integer | `balance_cents` floored at zero. The amount a new request can be settled against right now. |
| `currency` | string | Always `usd`. |
| `period.start` | string | ISO 8601 start of the current billing period: your subscription period while the subscription is active or trialing, otherwise the calendar month in UTC. |
| `period.spent_cents` | integer | Settled spend in the current period. Only settled charges count; an open hold is not spend yet. |
| `spend_cap.limit_cents` | integer or null | The legacy workspace spend cap, or `null` when none is set. `null` means no cap, never zero. |
| `spend_cap.hard` | boolean | `true` when the legacy cap refuses the work it covers, `false` when it only sends alerts. It never gates Model APIs inference. Always `false` when there is no cap. |
| `spend_cap.used_cents` | integer or null | What the cap is measured against: settled spend plus holds for other authorised work. `null` when there is no cap. |
| `spend_cap.remaining_cents` | integer or null | `limit_cents` minus `used_cents`, floored at zero, or `null` when there is no cap. |
| `spend_cap.gates_inference` | boolean | Always `false`. This workspace cap never refuses an inference request. |
| `plan_tier` | string | The entitled workspace tier, `free`, `core` or `enterprise`, the same value the `X-RateLimit-Tier` header carries. This is not the coding plan tier; read that from `/v1/usage`. |
| `as_of` | string | ISO 8601 time the numbers were read. |

Every amount is an integer in US cents. Nothing here is a dollar float, because the ledger stores cents and a float would be the first rounding in the chain.

## What decides whether a request runs

A credit-funded inference request is admitted against `available_cents`. Holds for requests already in flight are already taken out of that number, so a long request on a thin balance can show a small `available_cents` and a positive `held_cents` at the same time; when the request settles, the unused part of its hold returns to the balance.

The legacy workspace spend cap does not enter this decision, which is why `gates_inference` is always `false`. A [per-key spending limit](/docs/api-reference/authentication) is separate and can refuse requests on that key.

A request refused for insufficient credits answers `402` with `code: "insufficient_credits"` and `error.current_balance_cents`, the same balance this endpoint reports, plus a `topup_url`. See [Errors](/docs/api-reference/errors).

The credit amounts above appear directly in `error` on OpenAI-compatible refusals. The [Anthropic Messages error envelope](/docs/api-reference/anthropic-messages#errors) preserves only the error type and message text.

## Who can read it

Use a workspace API key created in Settings. A key that can spend the balance can read it, because the number is already disclosed to that key inside every `402` it earns.

Adding funds, changing the plan and reading invoices stay with the account owner in the dashboard; there is no write surface here.

## Freshness and limits

The response carries `Cache-Control: no-store` and is read from the ledger on every call. Use `as_of` rather than your own clock when you compare two reads.

On top of your workspace's [per-key rate limit](/docs/api-reference/rate-limits) this endpoint has its own allowance of 60 reads a minute per key. Poll it when you need a decision, not on a timer. The `x-ratelimit-*` headers on the response describe the plan bucket, so a boot-time read here also tells you your remaining request quota. During a rate-limit store outage, both limits are counted per server.

## Errors

| Status | `error.code` | Meaning |
| - | - | - |
| `401` | `invalid_api_key` | The key is missing, malformed, unknown or expired. |
| `403` | `auth_error` | The key is deactivated. Replace it with an active workspace key. |
| `403` | `credits_scope_violation` | A legacy key is limited to part of the workspace. Use a workspace key. |
| `405` | `method_not_allowed` | Only `GET` is served. The `Allow` header says so. |
| `429` | `rate_limit_exceeded` | The plan bucket or the 60-a-minute endpoint bucket. Honour `Retry-After`. |
| `503` | `limiter_unavailable` | The rate-limit store is not configured, or the connection gate could not evaluate the request. Retry shortly; nothing was charged. |
| `503` | `datastore_unavailable` | The ledger could not be read. Retry with backoff; the balance was not changed. |
