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

# AI credits

> Show the user's shared monthly AI balance and handle running out.

Every cskn user has one monthly pool of AI credits, shared by **all** apps. Each AI action has a fixed price. Show the price next to the button and the remaining balance below it, so the user knows what an action costs before taking it.

```tsx theme={null}
import { api, useAiCredits, useRefreshAiCredits, aiCreditsExhausted } from '@cskn/sdk';

function GeneratePlan() {
  const { data: credits } = useAiCredits();
  const refresh = useRefreshAiCredits();
  const cost = credits?.prices['fitness.workout-plan'];

  const generate = async () => {
    try {
      await api.post('/apps/fitness/ai/workout-plan', {});
    } catch (error) {
      const exhausted = aiCreditsExhausted(error);
      if (exhausted) {
        alert(`You're out of AI credits until ${new Date(exhausted.resetAt).toLocaleDateString()}.`);
      }
    } finally {
      void refresh();
    }
  };

  return (
    <>
      <button onClick={generate}>
        Generate plan{cost != null && ` · ${cost} credits`}
      </button>
      {credits && <small>{credits.remaining} of {credits.limit} credits left this month</small>}
    </>
  );
}
```

## useAiCredits

Refetched every time it mounts, because other apps spend from the same pool. Call `useRefreshAiCredits()` after your own app spends.

<ResponseField name="used / limit / remaining" type="number">
  This month's spend, cap and balance.
</ResponseField>

<ResponseField name="period" type="string">
  The month, `YYYY-MM` in UTC.
</ResponseField>

<ResponseField name="resetAt" type="string">
  ISO time of the next reset: the first of next month, UTC.
</ResponseField>

<ResponseField name="prices" type="Record<string, number>">
  Credits per action, keyed `app.feature`, for example `{ 'ask.message': 1 }`.
</ResponseField>

<ResponseField name="enforced" type="boolean">
  While `false`, the server counts spending but never refuses a request.
</ResponseField>

Your app sees totals only. How much other apps spent is never returned to it.

## Handling a refusal

When the pool is empty, AI endpoints answer **402** with `code: 'AI_CREDITS_EXHAUSTED'`. `aiCreditsExhausted(error)` returns that body, or `null` for any other error.

| Status | Meaning | What to tell the user |
| - | - | - |
| `402` + `AI_CREDITS_EXHAUSTED` | The month's credits are spent. | When they come back (`resetAt`). Retrying will not help. |
| `429` | Too many requests in the last minute. | Try again shortly. |

The 402 body also carries `feature`, `cost`, `used`, `limit` and `remaining`.

Without React, use `getAiCredits()`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.