# Authentication

> Secure your Zavu API requests with Bearer tokens. Test and live keys, project scoping, key rotation and best practices for keeping production

## API Keys

All API requests require authentication using a Bearer token in the Authorization header.

```bash
Authorization: Bearer zv_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

## Key Types

| Prefix | Environment | Usage |
|--------|-------------|-------|
| `zv_live_` | Production | Real messages, real costs |
| `zv_test_` | Sandbox | WhatsApp only, to your team's own phones, from Zavu's sandbox number |

> **Warning:**
> **A test key delivers real messages.** It does not simulate anything: the
> message goes to WhatsApp and arrives on the handset. What makes it safe is who
> it can reach, not whether it sends.
>
> A `zv_test_` key ignores your senders entirely and sends WhatsApp from Zavu's
> shared sandbox number, only to a phone belonging to someone on your team. Any
> other channel is refused with `400`, an unlisted recipient with `403`, and
> `POST /v1/calls` with `400`. Use it to exercise the API before you have a
> WhatsApp Business Account of your own; use a `zv_live_` key for everything else.
>
> See [Sending limits](https://www.zavu.dev/docs/concepts/sending-limits#test-mode-keys).

## Creating API Keys

Your account is created with a live key already in it. It is on the dashboard
home, masked, with a button to show it and a button to copy it — there is
nothing to create before your first call.

To create another (one per machine, or one per environment):

1. Log in to your [Zavu Dashboard](https://dashboard.zavu.dev)
2. Navigate to **API Keys**
3. Click **Create API Key**
4. Give it a descriptive name (e.g., "Production Server", "Development")
5. Copy the key

> **Info:**
> A key can be shown again from the **API Keys** screen whenever you need it, so
> losing the tab you created it in does not lose the key. Keys created before
> this was possible are the exception: only their prefix is stored, and the way
> back is to create a new one. Revoking a key stops it authenticating and stops
> it being shown.

## Using API Keys

### In HTTP Requests

Include the key in the `Authorization` header:

```bash
curl https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer zv_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json"
```

### In SDKs

```typescript
import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev({
  apiKey: process.env['ZAVUDEV_API_KEY'], // This is the default and can be omitted
});
```

## Optional Headers

| Header | Description | Example |
|--------|-------------|---------|
| `Zavu-Sender` | Override the default sender | `snd_abc123` |

Idempotency is not a header: send `idempotencyKey` in the request body, as shown below.

### Zavu-Sender Header

Override the default sender for a specific request:

```bash
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer zv_live_xxx" \
  -H "Zavu-Sender: snd_abc123" \
  -H "Content-Type: application/json" \
  -d '{"to": "+56912345678", "text": "Hello!"}'
```

### Idempotency Keys

Prevent duplicate message sends due to network retries:

```bash
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer zv_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+56912345678",
    "text": "Your order #12345 has shipped!",
    "idempotencyKey": "order-12345-shipped"
  }'
```

If you retry this request with the same `idempotencyKey`, you'll receive a `409 Conflict` whose body is the original message, in its current state, instead of sending a duplicate. The SDKs raise that `409` as a `ConflictError` without retrying it; the original message is on the error's `error` property.

## Security Best Practices

> **Warning:**
> Never expose your API keys in client-side code, public repositories, or browser applications.

### Do's

- Store keys in environment variables
- Use different keys for development and production
- Rotate keys periodically (every 90 days recommended)
- Use the minimum permissions needed
- Monitor key usage in your dashboard

### Don'ts

- Don't commit keys to version control
- Don't share keys via email or chat
- Don't use production keys in development
- Don't embed keys in mobile apps or frontends

## Frontend Integration

The Zavu API uses secret API keys that **must never be exposed in client-side code**. If you include your key in a browser app, anyone can open DevTools and steal it.

Instead, use the **Backend-for-Frontend (BFF) pattern**: your frontend calls your own server endpoint, and your server calls the Zavu API.

### Server-side proxy examples

```typescript
// app/api/send-message/route.ts
import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev(); // reads ZAVUDEV_API_KEY from env

export async function POST(request: Request) {
  const { to, text } = await request.json();

  const result = await zavu.messages.send({ to, text });
  return Response.json(result);
}
```

```typescript
import express from 'express';
import Zavudev from '@zavudev/sdk';

const app = express();
app.use(express.json());

const zavu = new Zavudev(); // reads ZAVUDEV_API_KEY from env

app.post('/api/send-message', async (req, res) => {
  const { to, text } = req.body;

  const result = await zavu.messages.send({ to, text });
  res.json(result);
});
```

### Frontend example

```typescript
async function sendMessage(to: string, text: string) {
  const res = await fetch('/api/send-message', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ to, text }),
  });
  return res.json();
}
```

> **Info:**
> Add your own authentication and validation to the proxy endpoint. The examples above are simplified for clarity.

## Key Access

Every API key has full access to the project it belongs to: it can send, read and change anything that project holds. Keys cannot be scoped to a subset of operations today.

To limit what a piece of code can reach, give it a key from its own project. A [sub-account](https://www.zavu.dev/docs/concepts/sub-accounts) is a separate project with its own keys, its own senders and data, and an optional spending limit.

## Revoking Keys

If a key is compromised:

1. Go to **Settings** → **API Keys**
2. Find the compromised key
3. Click **Revoke**
4. Create a new key
5. Update your applications

Revoked keys are immediately invalidated and cannot be restored.

## Error Responses

| Status | Error | Description |
|--------|-------|-------------|
| `401` | `unauthorized` | Missing or invalid API key |
| `403` | varies | The account is not allowed to do this, for example `sending_suspended` or `plan_limit_reached`. The `code` says which |
| `429` | `rate_limit_exceeded` | Too many requests |

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  }
}
```
