> For the complete documentation index, see [llms.txt](https://series-1.gitbook.io/rundot-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://series-1.gitbook.io/rundot-docs/v5.22.0/readme/credits.md).

# Credits API (BETA)

Read the player's AI **Creator credits** and drive a plan paywall. When a game is **player-billed**, the AI generation it triggers (`ai`, `imageGen`, `videoGen`) is charged to the signed-in player's Creator credits instead of the game owner's. This API lets you show the player their balance and plan, and resolve a deficit when they run out.

{% hint style="warning" %}
All SDK methods can reject; unhandled rejections crash the app. Always wrap SDK calls in `try/catch` or attach a `.catch()` handler. See [Error Handling](/rundot-docs/v5.22.0/readme/error-handling.md) for details.
{% endhint %}

> **Note:** Creator credits are **not** the same as RunBucks. RunBucks are the in-game hard currency, handled by the [Purchases API](/rundot-docs/v5.22.0/readme/purchases.md) (`RundotGameAPI.iap`). Creator credits are the AI credits that power generation in player-billed games. This API (`RundotGameAPI.credits`) only deals with Creator credits.

## Is My Game Player-Billed?

A game's billing mode is set by the platform, not by your game. `getBillingContext()` tells you how the current session bills AI generation. It is **informational only** — it never gates the other methods, so you can call any method regardless of billing mode and decide what to render yourself.

```typescript
import RundotGameAPI from '@series-inc/rundot-game-sdk/api'

const ctx = await RundotGameAPI.credits.getBillingContext()
if (ctx.billedTo === 'player') {
  // The player pays for AI generation — show their balance / a top-up entry point.
  if (ctx.playerChargesEnabled) {
    // Charges are actively enforced right now.
  }
}
```

## Quick Start

```typescript
import RundotGameAPI from '@series-inc/rundot-game-sdk/api'

// Show the player's spendable Creator credits.
const balance = await RundotGameAPI.credits.getBalance()
console.log(`${balance.available} / ${balance.total} credits`)

// Let the player upgrade their plan.
const result = await RundotGameAPI.credits.openPaywall()
if (result.outcome === 'purchased') {
  updateCreditsUI(result.balance)
}
```

## Handling Exhaustion

When a player-billed game runs the player out of credits mid-generation, the host can automatically open the paywall and retry the call — so for most games you don't have to do anything. Two flags (both **on** by default) control this:

* `setAutoPaywallOnExhaustion(enabled)` — on exhaustion, automatically open the paywall.
* `setAutoRetryOnPurchase(enabled)` — after a paywall purchase, automatically retry the original generation once and resolve it with the result.

| autoPaywall      | autoRetry        | What happens on exhaustion                                                                                                              |
| ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `true` (default) | `true` (default) | Paywall opens. On purchase, the original call retries and resolves with the result. On cancel, it rejects with `CreditsExhaustedError`. |
| `true`           | `false`          | Paywall opens. On purchase, the call **rejects** (`paywallOutcome: 'purchased'`) so you can retry it yourself. On cancel, it rejects.   |
| `false`          | *(ignored)*      | The call rejects immediately (`paywallShown: false`).                                                                                   |

These setters are **synchronous** — they update local SDK state and push it to the host fire-and-forget.

```typescript
// Drive your own credits UX instead of the built-in paywall:
RundotGameAPI.credits.setAutoPaywallOnExhaustion(false)

try {
  const image = await RundotGameAPI.imageGen.generate({ prompt: 'a fox' })
  show(image)
} catch (err) {
  if (isCreditsExhaustedError(err)) {
    // Show your own out-of-credits UI, then open the paywall on demand.
    const result = await RundotGameAPI.credits.openPaywall({ highlightTier: 'plus' })
    if (result.outcome === 'purchased') {
      retryGeneration()
    }
  }
}
```

## Reacting to Balance Changes

`onBalanceChanged` fires when the player's spendable balance changes (for example after a paywall purchase). It returns an unsubscribe function.

```typescript
const unsubscribe = RundotGameAPI.credits.onBalanceChanged((balance) => {
  updateCreditsUI(balance)
})

// later
unsubscribe()
```

## API Reference

<table><thead><tr><th width="320">Method</th><th>Returns</th><th>Description</th></tr></thead><tbody><tr><td><code>getBillingContext()</code></td><td><code>Promise&#x3C;CreditsBillingContext></code></td><td>How the current game bills AI generation. Informational only — never gates other calls.</td></tr><tr><td><code>getBalance()</code></td><td><code>Promise&#x3C;CreditBalance></code></td><td>The player's spendable / total Creator credits and free-daily info.</td></tr><tr><td><code>getSubscription()</code></td><td><code>Promise&#x3C;CreditSubscription></code></td><td>The player's current Creator plan (paid tier), if any.</td></tr><tr><td><code>getPlans()</code></td><td><code>Promise&#x3C;CreditPlansCatalog></code></td><td>The available plans, top-up packs, and the free-daily grant.</td></tr><tr><td><code>openPaywall(options?)</code></td><td><code>Promise&#x3C;CreditsPurchaseResult></code></td><td>Open the host paywall. Resolves once the player closes it.</td></tr><tr><td><code>setAutoPaywallOnExhaustion(enabled)</code></td><td><code>void</code></td><td>Toggle the automatic exhaustion paywall (default <code>true</code>). Synchronous.</td></tr><tr><td><code>getAutoPaywallOnExhaustion()</code></td><td><code>boolean</code></td><td>Current auto-paywall setting.</td></tr><tr><td><code>setAutoRetryOnPurchase(enabled)</code></td><td><code>void</code></td><td>Toggle automatic retry after an exhaustion purchase (default <code>true</code>). Synchronous.</td></tr><tr><td><code>getAutoRetryOnPurchase()</code></td><td><code>boolean</code></td><td>Current auto-retry setting.</td></tr><tr><td><code>onBalanceChanged(listener)</code></td><td><code>Unsubscribe</code></td><td>Subscribe to balance changes. Returns an unsubscribe function.</td></tr></tbody></table>

## Types

These types are exported from the package root, `@series-inc/rundot-game-sdk` (not the `/api` subpath).

### `CreditsBillingContext`

| Field                  | Type                  | Description                                                              |
| ---------------------- | --------------------- | ------------------------------------------------------------------------ |
| `billedTo`             | `'owner' \| 'player'` | Who AI generation in this game is billed to.                             |
| `playerChargesEnabled` | `boolean`             | `true` only when `billedTo === 'player'` AND charge enforcement is live. |

### `CreditBalance`

| Field       | Type                          | Description                                                                         |
| ----------- | ----------------------------- | ----------------------------------------------------------------------------------- |
| `available` | `number`                      | Credits the player can spend right now.                                             |
| `total`     | `number`                      | The "available / total" denominator.                                                |
| `freeDaily` | `CreditFreeDailyInfo \| null` | Free-daily grant info, or `null` when the player isn't eligible (e.g. a paid plan). |

`CreditFreeDailyInfo`: `{ dailyCredits: number; availableCredits: number; nextResetAt: string }` (`nextResetAt` is an ISO timestamp).

### `CreditSubscription`

| Field              | Type                 | Description                                     |
| ------------------ | -------------------- | ----------------------------------------------- |
| `status`           | `'active' \| 'none'` | Whether the player has an active Creator plan.  |
| `tier`             | `string \| null`     | The paid plan tier (e.g. `'plus'`), or `null`.  |
| `monthlyCredits`   | `number \| null`     | Credits granted per period for the active plan. |
| `creditsRemaining` | `number \| null`     | Credits remaining in the current period.        |
| `renewsAt`         | `string \| null`     | ISO renewal/expiry timestamp.                   |
| `willRenew`        | `boolean`            | Whether the plan will auto-renew.               |

### `CreditPlansCatalog`

| Field              | Type                | Description                                              |
| ------------------ | ------------------- | -------------------------------------------------------- |
| `plans`            | `CreditPlan[]`      | Available subscription plans.                            |
| `topUpPacks`       | `CreditTopUpPack[]` | One-time top-up packs (purchase UI is a future release). |
| `freeDailyCredits` | `number`            | The free-daily grant amount.                             |

`CreditPlan`: `{ tier: string; productId: string; monthlyCredits: number; rolloverDays: number }`. `CreditTopUpPack`: `{ productId: string; credits: number }`.

### `OpenPaywallOptions`

| Field           | Type                 | Required | Description                                                                    |
| --------------- | -------------------- | -------- | ------------------------------------------------------------------------------ |
| `focus`         | `'plans' \| 'topup'` | No       | Which surface to focus. `'topup'` is a future surface (currently shows plans). |
| `highlightTier` | `string`             | No       | A tier id to visually highlight in the plan picker.                            |
| `screenName`    | `string`             | No       | Screen/route name for analytics attribution.                                   |

### `CreditsPurchaseResult`

| Field     | Type                                      | Description                                                                       |
| --------- | ----------------------------------------- | --------------------------------------------------------------------------------- |
| `outcome` | `'purchased' \| 'cancelled' \| 'pending'` | What the player did in the paywall.                                               |
| `balance` | `CreditBalance \| null`                   | The refreshed balance after the paywall closed, or `null` if it couldn't be read. |

### `CreditsExhaustedError`

Thrown from `ai` / `imageGen` / `videoGen` calls when a player-billed game runs the player out of credits and the deficit wasn't resolved. Use the exported `isCreditsExhaustedError(err)` / `asCreditsExhaustedError(err)` helpers to detect and narrow it.

| Field            | Type                                              | Description                                                 |
| ---------------- | ------------------------------------------------- | ----------------------------------------------------------- |
| `code`           | `'CREDITS_EXHAUSTED'`                             | Stable discriminator.                                       |
| `billedTo`       | `'owner' \| 'player'`                             | Who the generation was billed to.                           |
| `paywallShown`   | `boolean`                                         | Whether the host opened the paywall before giving up.       |
| `paywallOutcome` | `'purchased' \| 'cancelled' \| 'pending' \| null` | The paywall result (`null` when `paywallShown` is `false`). |

## Best Practices

* Treat `getBillingContext()` as a hint for what to render — never as a gate. Any method works regardless of billing mode.
* Leave the auto-paywall on for the simplest experience; only turn it off when you want a custom out-of-credits UX.
* Use `onBalanceChanged` to keep your credits UI in sync rather than polling `getBalance()`.
* Always handle the `'cancelled'` outcome — players will dismiss the paywall.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://series-1.gitbook.io/rundot-docs/v5.22.0/readme/credits.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
