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

# One paid action

> Put one paid action on the Unprice money path.

Start with the action in your product that can burn a customer's credits, budget, or usage
allowance. Prove that one path before you migrate billing or enforce every feature.

The first integration has one job:

> Decide whether one paid action should run, then keep the evidence that explains what happened.

## 1. Pick the paid action

Choose a narrow action with a clear customer and feature:

* `ai.generate` for an LLM call
* `api.request` for a billable API request
* `workflow.run` for a multi-step job
* `export.create` for a costly export

Write down the feature slug and event slug you want your app to use:

| Product concept | Example                                        |
| --------------- | ---------------------------------------------- |
| Feature slug    | `ai-messages`, `ai-generations`, or `ai-tools` |
| Event slug      | `completions`                                  |
| Customer ID     | `cus_1234567890`                               |

Use the event slug for the broad thing your app observed. Use feature slugs for the specific
product features that read from that event.

## 2. Publish a plan version

In the dashboard:

1. Create a plan, for example `Pro`.
2. Add the feature that gates or meters the paid action.
3. Configure the meter if the feature is usage-based.
4. Publish the plan version.

The plan version is the commercial rule your customer is pinned to. Future pricing experiments can
ship as new plan versions without silently moving existing customers.

## 3. Install the SDK

<CodeGroup>
  ```bash npm theme={null}
  npm install @unprice/api
  ```

  ```bash pnpm theme={null}
  pnpm add @unprice/api
  ```
</CodeGroup>

```ts theme={null}
import { Unprice } from "@unprice/api";

const unprice = new Unprice({
  token: process.env.UNPRICE_TOKEN
});
```

Keep the API key on your server. Do not expose it in browser code.

## 4. Create or map one customer

Use `customers.signUp` when Unprice should provision the customer, subscription, entitlements,
billing periods, and wallet from the plan version.

```ts theme={null}
const { result, error } = await unprice.customers.signUp({
  name: "Acme Inc.",
  email: "buyer@example.com",
  planSlug: "pro",
  externalId: "acct_123",
  successUrl: "https://yourapp.com/billing/success",
  cancelUrl: "https://yourapp.com/billing/cancel"
});

if (error) {
  throw new Error(error.message);
}

const customerId = result.customerId;
```

If you already have customers in your app, store the returned Unprice `customerId` beside your own
account ID.

## 5. Run the decision in shadow

Call `access.check` beside your current logic before the paid action runs. It is read-only, so it is
safe to log and compare before you let Unprice enforce anything.

```ts theme={null}
const { result, error } = await unprice.access.check({
  customerId,
  featureSlug: "ai-messages"
});

if (error) {
  throw new Error(error.message);
}

console.log({
  unpriceWouldAllow: result.allowed,
  usage: result.usage,
  limit: result.limit,
  spending: result.spending
});
```

Use this first when you need confidence that the plan version, entitlement, usage, and wallet state
match the product behavior you already trust.

## 6. Enforce when the decision is ready

When the usage amount is known at the decision point, use `usage.consume` before the paid work runs.
It synchronously applies the usage event and returns the allow or deny decision.

```ts theme={null}
const { result, error } = await unprice.usage.consume({
  customerId,
  featureSlug: "ai-messages",
  eventSlug: "completions",
  idempotencyKey: request.id,
  properties: {
    aiMessages: 1,
    inputTokens: 1840,
    outputTokens: 620,
    model: "gpt-4.1-mini"
  }
});

if (error) {
  throw new Error(error.message);
}

if (!result.allowed) {
  return new Response("Usage limit reached", { status: 429 });
}

return runAiGeneration();
```

Use the same `idempotencyKey` when retrying the same request.

## 7. Use a budgeted run for multi-step work

If the action can spend across multiple steps, reserve a budget before the work starts, report the
usage created inside the workload, then close the run so unused reserved funds are released:

```ts theme={null}
// Reserve spend before the workload creates cost.
const { result: run, error: startError } = await unprice.runs.start({
  customerId,
  budgetAmountMinor: 5000,
  idempotencyKey: `run:${request.id}`,
  workloadType: "workflow",
  workloadId: "wf_summary_123"
});

if (startError) {
  throw new Error(startError.message);
}

// Only start the paid work after the reservation is running.
if (run.status !== "running") {
  return new Response(`Run rejected: ${run.status}`, { status: 429 });
}

let finalStatus: "completed" | "failed" = "completed";

try {
  // Report each billable step against the running budget.
  const { result: usage, error: consumeError } = await unprice.runs.consume({
    runId: run.runId,
    featureSlug: "ai-tools",
    eventSlug: "completions",
    idempotencyKey: `run:${request.id}:step:summary`,
    properties: {
      aiTools: 3,
      inputTokens: 1840,
      outputTokens: 620,
      step: "summary"
    }
  });

  if (consumeError) {
    throw new Error(consumeError.message);
  }

  if (!usage.accepted) {
    finalStatus = "failed";
    return new Response(`Run rejected: ${usage.reason}`, { status: 429 });
  }

  await runSummaryWorkflow();
} catch (error) {
  finalStatus = "failed";
  throw error;
} finally {
  // Always close the run so unused reservation funds are released.
  const { error: endError } = await unprice.runs.end({
    runId: run.runId,
    status: finalStatus
  });

  if (endError) {
    console.error(endError.message);
  }
}
```

This keeps the budget envelope separate from the workload executor: your app owns the workflow;
Unprice owns the customer spend authorization and evidence.

## Next steps

<CardGroup cols={2}>
  <Card title="Choose the runtime call" icon="code" href="/quickstart/choose-operation">
    Compare `access.check`, `usage.record`, `usage.consume`, and budgeted runs.
  </Card>

  <Card title="TypeScript SDK" icon="terminal" href="/libraries/ts/sdk/overview">
    Learn the SDK result shape, retries, base URL, and telemetry options.
  </Card>
</CardGroup>
