> For the complete index of the 402pay docs, see [llms.txt](https://developer.402pay.co/llms.txt).

# Authentication

Authenticate with a secret key, scope it per resource, and keep it safe.

Send your secret key as a bearer token in the `Authorization` header. A key belongs to one business, and every request made with it acts as that business.

Authenticated request, cURL:

```bash
curl "https://dash.402pay.co/api/v1/payments?limit=1" \
  -H "Authorization: Bearer $PAY402_SECRET_KEY"
```

Authenticated request, Node.js:

```js
const response = await fetch("https://dash.402pay.co/api/v1/payments?limit=1", {
  headers: {
    Authorization: `Bearer ${process.env.PAY402_SECRET_KEY}`,
  },
});
const { data } = await response.json();
```

Authenticated request, Python:

```python
import os

import requests

response = requests.get(
    "https://dash.402pay.co/api/v1/payments?limit=1",
    headers={
        "Authorization": f"Bearer {os.environ['PAY402_SECRET_KEY']}",
    },
)
data = response.json()["data"]
```

## Key types

| Prefix | Kind | Use |
| --- | --- | --- |
| `402s_test_` | Secret, test | Server-side requests while you build. |
| `402s_live_` | Secret, live | Server-side requests in production. |
| `402p_…` | Publishable | Reserved for browser-side use later. Nothing accepts it today: an endpoint that needs a key answers it with 401 `invalid_api_key`, so you can ignore it for now. |

> Keep secret keys on your server and out of source control. Only a hash is stored, so a lost secret can't be shown again: create a new key, then revoke the old one.

## Test keys and real money

A test key reads everything a live key can. On a live business, whose payments are real money, it can't move money or change what customers see or pay. These answer 403 `test_mode_unavailable` until test keys get data of their own:

- Creating a payment, opening a checkout or requesting a remainder.
- Creating, changing, archiving, restoring or deleting a payment link.
- Changing checkout settings, such as the coins you accept or who pays the fee.
- Accepting or canceling a payment.
- Blocking or unblocking a customer. Other customer fields, such as a note, can still change.

Use a live key for them. Webhooks, events and everything else a key can reach work as before, and on a simulated business, or one on test networks, a test key does everything a live key does.

## Restricted keys

A secret key has full access, or a level per resource: none, read or write. `GET` requests need read and every other method needs write, so a key that only reports on payments can't create one.

| Resource | Covers |
| --- | --- |
| Payments | Payments and metrics. |
| Links | Payment links. |
| Customers | Customer records. |
| Wallet | Balances, addresses and notes. |
| Webhooks | Endpoints, deliveries and test events. |
| Events | The event log. Read only. |
| Checkout settings | Accepted coins, rails and redirects. |

> Money never moves on an API key. Sends from your wallet happen in the dashboard, signed in your browser with your encryption password.

## Errors

| Status | Code | When |
| --- | --- | --- |
| 401 | `unauthenticated` | No key was sent. The response has a `WWW-Authenticate: Bearer` header. |
| 401 | `invalid_api_key` | The key is invalid or revoked, or it's a publishable key. |
| 401 | `invalid_api_key` | The `Authorization` header isn't `Bearer`, a space and the key, such as when the `Bearer` prefix is missing. |
| 403 | `business_forbidden` | The request also sent a `402pay-Business` header naming another business. A key acts only as its own business, so leave the header out. |
| 403 | `permission_denied` | A restricted key doesn't have the access the route needs. |
| 403 | `test_mode_unavailable` | On a live business, a test key tried to move money or change what customers see or pay. |
| 403 | `session_required` | The route is for the dashboard only, such as managing API keys or sending from your wallet. |
