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

# Your first access decision

> Create an offer, check access, then change the offer

Five steps. The last one is the point: you'll change what you sell and watch an existing customer keep what they bought.

## Prerequisites

* Node.js 18 or later
* A Kerne app id and a secret key (`sk_test_...`), from the dashboard
* Stripe connected only if you want to test a paid subscription — an access decision doesn't need it

## 1. Create an offer

Offers live in the dashboard, never in your code — this is you configuring your business, not something your app does at runtime.

Under **Billing → Catalog**, create a product, then add a plan to it. Mark that plan **default** and keep it active: a customer with no subscription resolves to your default plan, which is what lets a brand-new signup pass a check at all.

## 2. Give it an entitlement

Add a feature with the key exactly `export_pdf`, of type **Boolean**. On the plan, set it to **enabled**.

That's the promise. Your code will ask about `export_pdf` and never about the plan's name.

<Note>
  If `export_pdf` isn't on the plan a customer is on, the check returns `false`. That's the normal
  "not sold to this customer" answer, not an error.
</Note>

## 3. Connect a customer

```bash theme={"system"}
npm install @kerne/server
```

The secret key belongs on your server only. It can act on behalf of any customer, so it must never reach a browser bundle.

```typescript theme={"system"}
// src/lib/kerne.ts
import { Kerne } from '@kerne/server';

export const kerne = new Kerne({
  appId: process.env.KERNE_APP_ID!,
  secretKey: process.env.KERNE_SECRET_KEY!,
});
```

Create a user. Its id is what every later call names as the `customer`:

```typescript theme={"system"}
const { user } = await kerne.auth.signup({
  email: 'alice@example.com',
  password: 'a-strong-password',
});
// user.id -> the customer
```

Signing up through Kerne already registers the customer behind the scenes — there's no separate step. Already have your own auth (Clerk, Supabase, your own backend)? Skip `auth.signup()` entirely and pass your own user id (or an org id, for a company you bill as one unit) as `customer` directly. `allows()`/`check()` work immediately, falling back to your default plan if that id doesn't exist yet. For a write that needs the record to exist first — `consume()`, `reportUsage()` — call `kerne.customers.upsert({ externalId, email })` once before it, or pass `email` on `checkout()` to create it there.

## 4. Check access

```typescript theme={"system"}
export async function exportPdfRoute(req, res) {
  const userId = req.user.id;

  if (!(await kerne.allows('export_pdf', { customer: userId }))) {
    return res.status(403).json({ error: 'upgrade_required' });
  }

  return res.json(await renderPdf());
}
```

`allows()` returns a boolean and never throws for a denial. With a secret key there's no implicit "current user", so always pass the customer you mean.

In React, the same decision without naming a customer — it resolves the logged-in session's own:

```tsx theme={"system"}
import { KerneProvider, Access } from '@kerne/react';

<KerneProvider appId={process.env.NEXT_PUBLIC_KERNE_APP_ID!}>
  <Access featureKey="export_pdf" fallback={<a href="/pricing">Upgrade to export</a>}>
    <button onClick={exportPdf}>Export as PDF</button>
  </Access>
</KerneProvider>
```

A client check is a convenience. The authoritative one stays on your backend.

## 5. Change the offer

Here's what Kerne is actually for.

Go back to the plan and turn `export_pdf` **off**, then publish it as a new version.

Now check both:

* **Alice, who subscribed before the change** — still `true`. She's attached to the version she received.
* **A customer who signs up now** — `false`. They get the current version.

You changed what you sell. You didn't change what you'd already sold, and you didn't deploy anything.

That's the default, not a setting you remembered to enable. See [Offers & versions](/concepts/monetization/products-and-plans).

## Troubleshooting

| Symptom                      | Likely cause                                                                                              |
| ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| `allows()` is always `false` | The feature isn't on the customer's plan; no active default plan and no subscription; wrong `customer` id |
| `401` on a backend call      | Wrong secret key or app id, or the key was sent from the browser                                          |
| `checkout()` fails           | Stripe not connected, or a plan id passed where a price id is required                                    |

## Next

<CardGroup cols={2}>
  <Card title="How Kerne works" icon="map" href="/concepts/overview">
    What Kerne holds, and how a decision resolves.
  </Card>

  <Card title="Design your pricing" icon="compass" href="/guides/billing/designing-pricing">
    Bundling, metering, and add-ons.
  </Card>

  <Card title="Connect your billing provider" icon="credit-card" href="/guides/getting-started/subscription-setup">
    Stripe, checkout, and the billing portal.
  </Card>

  <Card title="Add usage limits" icon="gauge" href="/guides/billing/enforcing-limits-without-breaking-ux">
    Allowances that block, or bill for the excess.
  </Card>
</CardGroup>
