# Authentication

> Which credential each Kanorio API accepts, how permissions are calculated, and how to rotate and revoke tokens safely.

> Markdown version of https://kanorio.com/en/developers/authentication. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Each Kanorio API is built for a different caller, and each accepts its own credential. Pick the credential by asking who the caller acts for.

| Credential | Prefix | Acts as | Accepted by | Use it for |
|---|---|---|---|---|
| Personal access token | `knr_pat_` | You, limited to the sites and permissions you chose | Management API, MCP | Your scripts, the CLI, coding assistants that take a key |
| Service account token | `knr_sa_` | The site itself, not a person | Management API, MCP | CI, schedulers and partner back ends that must keep running when people leave |
| OAuth access token | `knr_at_` | You, through an app you approved | MCP only | Assistants with built-in connectors, such as Claude or ChatGPT |
| Preview token | `knr_pv_` | Read-only access to one site's drafts | Content Delivery API | Draft mode in your own front end |
| Customer token | `knr_cat_` | One signed-in customer of one site | Customer API, gated Content Delivery API reads | Sign-in, purchased content and orders in your front end |

The Content Delivery API needs no credential for published content.

## Management API

Send the token as a Bearer credential on every request to `https://app.kanorio.com/api/v1`.

```bash
curl https://app.kanorio.com/api/v1/sites \
  -H "Authorization: Bearer knr_pat_..."
```

### Personal access tokens

Create them under [Account settings › API access](https://app.kanorio.com/account/api-access). When you create one you choose:

- **Sites.** Specific sites, or all sites including ones you create later. Creating new sites needs all sites.
- **Permissions.** A preset (read only, edit content, full access) or individual permissions.
- **Expiry.** Tokens always expire. You get an email and an inbox notice before they do.

A token's effective permission on a site is the intersection of what the token allows and what your role on that site allows. A full-access token still cannot edit a site where you are a viewer, and only the site owner can unpublish.

Some permissions are never granted to tokens, whatever preset you pick. These include sending or scheduling newsletters, exporting data, refunds, deleting contacts, and managing members or billing. An agent can draft a newsletter, but a person sends it from the dashboard.

### Service accounts

A service account belongs to a site, not to a person. It keeps working when the person who created it leaves the team, and revoking it revokes every token it issued at once.

1. Open [Site settings › Developer](https://app.kanorio.com/settings/developer) and add a service account. You can grant only permissions you have yourself.
2. Open it and issue a token. Name the token after where it will live, such as "GitHub Actions". The `knr_sa_` value is shown once.

Service accounts work inside their own site only. Account-level operations, such as creating sites or listing all your sites, return `403 FORBIDDEN`. Each operation in the [OpenAPI document](https://app.kanorio.com/api/v1/openapi.json) lists the callers it accepts in `x-kanorio-principals`; operations that include `SERVICE_ACCOUNT` accept service account tokens.

Narrowing a service account's permissions narrows its existing tokens immediately. Widening applies only to tokens issued afterwards.

### Scopes

Each operation in the [Management API reference](https://kanorio.com/en/developers/management-api) shows the permission it needs. The OpenAPI document carries the same value as `x-kanorio-required-scope`. A token without it gets `403 FORBIDDEN`. Call `GET /whoami` to see what the current token can do.

## MCP

The MCP server at `https://app.kanorio.com/api/mcp` accepts the same personal access and service account tokens, plus OAuth 2.1 access tokens. Assistants with built-in connectors discover the authorization server automatically and ask you to approve access in the browser. OAuth access tokens are bound to the MCP server and are rejected by the REST endpoints. See [MCP](https://kanorio.com/en/developers/mcp).

## Content Delivery API

Published content is public and edge-cached, so browsers can call it directly.

- **Browser calls from another origin** need that origin in **Allowed origins** under [Site settings › Developer](https://app.kanorio.com/settings/developer). `localhost` and `127.0.0.1` on any port are always allowed. CORS controls what browsers may read. It is not an authorization boundary, and drafts are never served without a preview token.
- **Draft preview.** Create a `knr_pv_` preview token in the same settings page, or with `POST /sites/{id}/preview-tokens`. Send it from your server as `X-Kanorio-Preview: knr_pv_...`. Preview responses bypass the cache and are never stored.

## Customer API

Customers sign in without a password. The site owner chooses which methods are on under **Site settings › Customer accounts**; the site document's `customerAccess.methods` lists the enabled ones in display order. Every method ends in the same `knr_cat_` session token.

- **Email code** (`email_code`, always on) and **magic link** (`email_link`): `POST /api/v1/customer/auth/challenge` with the email address sends the code. First get the bot check from `GET /api/v1/customer/auth/protection`: with the built-in check (`provider: "kanorio"`) solve it with `solveProtectionChallenge` from `@kanorio/sdk` and send the fields with the request; with the owner's Turnstile, reCAPTCHA or hCaptcha keys, render that widget with `siteKey` and `action` and send its token as `captchaToken`. Then `POST /api/v1/customer/auth/verify` with `{ "method": "code", "email", "code" }` or `{ "method": "link", "token" }`.
- **Google** (`google`): `POST /api/v1/customer/auth/oauth/start` with `{ "method": "google", "baseUrl": <your page's origin> }` returns the provider URL; redirect the browser there. Kanorio hosts the consent screen and sends the browser back to `baseUrl` with `?handoff=`. Exchange it within 60 seconds: `POST /api/v1/customer/auth/verify` with `{ "method": "handoff", "token" }`. If the Google account's email is not verified, the customer gets an email code instead and lands on your page with `?email=&verify=1`.
- **Passkeys** (`passkey`): `POST /api/v1/customer/auth/passkey/options`, run `navigator.credentials.get()` with the options, then `POST /api/v1/customer/auth/passkey/verify` with the assertion and `challengeId`. Signed-in customers manage their passkeys under `/api/v1/customer/passkeys`. Passkeys are bound to the site's primary domain, so the ceremony must run on that origin.

Handle these responses from `POST /api/v1/customer/auth/challenge` in your sign-in UI:

| Code | What to do |
|---|---|
| `429 RATE_LIMITED` | Wait `retryAfter` seconds, then let the customer try again |
| `403 BOT_PROTECTION_FAILED` | Fetch a new challenge (or reset the widget) and resend |
| `503 SERVICE_UNAVAILABLE` | Sign-in is briefly unavailable; show a retry |
| `503 SHARED_SENDER_PAUSED` | Email sign-in is paused on this site for now; offer passkeys or Google, or ask the customer to try later |

A `200` never reveals whether an email was sent. Kanorio adjusts the bot check with the site's sign-in volume: the challenge from `/auth/protection` may ask for a visible widget (`visible: true`) or a harder built-in puzzle, so always render from the challenge you fetched rather than assuming the invisible flow.

Send the token as `Authorization: Bearer knr_cat_...` on Customer API calls and on gated Content Delivery API reads.

A customer token only ever represents that customer on that site. It cannot read anyone else's data or call the Management API. The [`<kanorio-login>`](https://kanorio.com/en/developers/elements) element runs this flow for you.

## Rotate and revoke

- **Rotate** a personal access token from its menu. Kanorio issues a new token with the same settings and keeps the old one working for a grace period you choose: none, one hour or one day. Choose none if you suspect a leak.
- **Revoke** a token to stop it immediately. The next request with it gets `401 UNAUTHORIZED`. Work it already completed is not rolled back.
- Activity performed with any token appears in the site's activity log under that token's owner, or under the service account's name.

## Keep tokens safe

- Store tokens in environment variables or a secrets manager on the server. Never prefix them with `NEXT_PUBLIC_` or ship them in a mobile app.
- Never paste a token into an AI chat. Real tools read it from configuration.
- Use one token per tool and one service account per integration, so you can revoke exactly one.
- Give each token the narrowest sites and permissions that work.
