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

# Gating billable actions

> Use JazaActionButton to block taps when balance is low, then enforce jaza.consume on your backend.

`JazaActionButton` answers one UX question: **does this wallet have enough credits for `featureCode`?** It never debits the wallet. Your host must call `jaza.consume` after a successful gate.

<Warning>
  Attackers can bypass UI. Treat ActionButton as a convenience, not authorization. Always meter on the server.
</Warning>

## Dual-SDK pattern

| Layer        | Responsibility                                                                               |
| ------------ | -------------------------------------------------------------------------------------------- |
| Expo         | `JazaActionButton` compares balance to the feature cost from the session / features snapshot |
| Insufficient | Opens the top-up (paywall) sheet; does **not** run `onPress`                                 |
| Sufficient   | Runs your `onPress` → call **your** BFF                                                      |
| Node BFF     | `jaza.consume({ customerId, featureCode, idempotencyKey })` with the secret key              |

## Mobile

```tsx theme={null}
import { JazaActionButton } from '@jazadev/react-native';

const API_BASE = process.env.EXPO_PUBLIC_API_URL!;

<JazaActionButton
  featureCode="SEND_MESSAGE"
  label="Send message"
  onPress={async () => {
    const res = await fetch(`${API_BASE}/api/messages`, {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text: 'Hello' }),
    });
    if (!res.ok) throw new Error('Send failed');
  }}
/>
```

Configure `SEND_MESSAGE` (code + credit cost) in the [dashboard](https://jaza.dev/dashboard) under **Features**. Costs are included in the `jaza.init` snapshot and refreshed on the client.

Optional children render props let you keep your own button styling while reusing the gate.

## Host consume

```ts theme={null}
// Inside POST /api/messages (after your auth + business logic)
await jaza.consume({
  customerId: req.user.jazaCustomerId,
  featureCode: 'SEND_MESSAGE',
  idempotencyKey: `msg_${messageId}`,
});
```

Handle insufficient credits on the server even if the UI already gated — the balance can change between tap and request. See [Consume credits](/guides/consume-credits).

When consume fails for insufficient credits, Jaza can also notify the mobile session over the client realtime channel so the SDK can open the paywall. You still return an error from your BFF.

## What not to do

* Do **not** call `jaza.consume` from the React Native app
* Do **not** invent client-side “debit” endpoints that only trust the device
* Do **not** use `Fuel*` or other unofficial component names — use `JazaActionButton`

## Related

* [Handshake](/guides/handshake)
* [Consume credits](/guides/consume-credits)
* [Top-up](/guides/top-up)
* [React Native SDK](/sdks/react-native)
