# Authentication

All API requests must include an API key. ZevPay uses a **key pair** model: each pair consists of a secret key and a public key.

## API key types

| Key Type | Prefix | Use on | Purpose |
|----------|--------|--------|---------|
| **Secret key** | `sk_live_` or `sk_test_` | Server only | Full access — initialize sessions, verify payments, manage resources |
| **Public key** | `pk_live_` or `pk_test_` | Client (browser) | Initialize sessions (inline checkout), load checkout UI, select payment methods |

::: danger Never expose your secret key
Secret keys grant full access to your account. Never include them in frontend code, public repositories, or client-side bundles.
:::

### What each key can do

| Endpoint | Secret key | Public key |
|----------|-----------|-----------|
| Initialize session | Yes | Yes (with origin validation) |
| Select payment method | Yes | Yes (with origin validation) |
| Get session | Yes | Yes (with origin validation) |
| Verify session | Yes | Yes |
| All other endpoints | Yes | No |

Public keys can initialize checkout sessions directly from the browser — this is how the [Inline Checkout SDK](/sdks/inline) works without requiring a backend. When `allowedDomains` is configured on the API key, the request's `Origin` header is validated against the whitelist.

## Sending your API key

Include the key in the `x-api-key` header:

```bash
curl https://api.zevpaycheckout.com/v1/checkout/session/initialize \
  -H "x-api-key: sk_test_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{"amount": 100000, "email": "test@example.com"}'
```

Alternatively, use the `Authorization` header with a `Bearer` prefix:

```bash
Authorization: Bearer sk_test_your_secret_key
```

## API key permissions

Each API key has a **permissions** array that controls which product APIs it can access. Permissions are managed from the [Business Dashboard](https://business.zevpay.ng).

### Available permissions

| Permission | Products | Account type | Description |
|------------|----------|-------------|-------------|
| `checkout` | Checkout Sessions | All | Initialize and manage checkout payment sessions |
| `invoices` | Invoices | Business only | Create and manage programmable invoices |
| `virtual_payid` | Static PayID, Dynamic PayID | All | Create and manage virtual PayIDs |
| `virtual_account` | Virtual Accounts | All | Create temporary bank accounts for collecting payments |
| `transfers` | Transfers | Business only | Programmatically send money from a wallet |

::: info Legacy keys
API keys created before the permissions system was introduced have an empty permissions array. These keys retain access to all non-transfer products automatically. Only the `transfers` permission must be explicitly granted.
:::

### Permissions and account types

Some products are only available to business accounts:

| Product | Personal account | Business account |
|---------|-----------------|------------------|
| Checkout Sessions | Yes | Yes |
| Virtual PayID | Yes | Yes |
| Virtual Accounts | Yes | Yes |
| Invoices | No | Yes |
| Transfers | No | Yes |

### How permissions are assigned

- **Checkout, invoices, virtual_payid, virtual_account** — Configured when creating or editing an API key in the [Business Dashboard](https://business.zevpay.ng)
- **Transfers** — Granted automatically when a [programmable-debit wallet](/guide/transfers#setup) is linked to the API key from the wallet settings

### Permission errors

If an API key lacks the required permission for an endpoint, the API returns:

```json
{
  "statusCode": 403,
  "message": "API key does not have 'transfers' permission.",
  "error": "Forbidden"
}
```

## Domain whitelisting

Public keys can be restricted to specific domains. When `allowedDomains` is configured on an API key, requests using the public key must include an `Origin` or `Referer` header matching one of the allowed domains.

Configure allowed domains in the **[Business Dashboard](https://business.zevpay.ng) > Developers > API Keys** section.

### Matching rules

| Pattern | Matches | Example |
|---------|---------|---------|
| `example.com` | Exact domain only | `https://example.com` |
| `*.example.com` | Any subdomain + the domain itself | `https://shop.example.com`, `https://example.com` |

### Example configuration

If you set `allowedDomains` to `["mystore.com", "*.mystore.com"]`:

```
✅ https://mystore.com           → allowed (exact match)
✅ https://shop.mystore.com      → allowed (wildcard match)
❌ https://evil-site.com         → rejected
❌ https://mystore.com.evil.com  → rejected
```

::: tip Recommendation
For production, always configure `allowedDomains` on your public keys to prevent unauthorized sites from using your API key.
:::

## Live vs test mode

| Mode | Key prefix | Real money? | Purpose |
|------|-----------|-------------|---------|
| **Test** | `sk_test_` / `pk_test_` | No | Development and testing |
| **Live** | `sk_live_` / `pk_live_` | Yes | Production payments |

Both modes use the same API base URL:

```
https://api.zevpaycheckout.com
```

The mode is determined entirely by which key you use.

## Key management

- Create, rotate, and deactivate keys from the [Business Dashboard](https://business.zevpay.ng)
- Each key pair shares a webhook secret for signature verification
- You can create multiple key pairs (e.g., one per environment or application)
- Configure permissions per key to follow the principle of least privilege
