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

# Authentication

> How API keys work, where to put them, and how to rotate one without downtime.

Every request carries your API key in an **`apikey` request header**.

```bash theme={null}
curl https://api.bulkneo.com/v1/instances \
  -H "apikey: YOUR_API_KEY"
```

That is the whole scheme. There is no OAuth flow, no token exchange, no expiry to refresh.

<Warning>
  It is **`apikey`**, not `Authorization: Bearer`, not `X-API-Key`, and never a query string parameter. A request without that header comes back `401 missing_api_key`.
</Warning>

## Creating a key

Keys are created in the portal, on the **API Keys** screen. You cannot create a key using another key — that is deliberate, so a leaked key can never mint replacements that survive you revoking it.

When you create one, the full key is shown **once**:

<Warning>
  We store only a hash of your key. Nobody can retrieve the original afterwards — not you, not our support team. Copy it into your secret store immediately. If you lose it, revoke it and create a new one.
</Warning>

After that, the key list shows only a short prefix, the label you gave it, when it was created and when it was last used. That is enough to tell two keys apart without exposing either.

## Where to keep it

<Columns cols={2}>
  <Card title="Do" icon="check">
    * Keep it on your **server**, in an environment variable or secret manager.
    * Give each system its own key, labelled, so you can revoke one without touching the others.
    * Rotate it if it ever appears in a log, a screenshot, a chat message or a git commit.
  </Card>

  <Card title="Do not" icon="xmark">
    * Put it in a browser, a mobile app, or anything a user can view source on.
    * Put it in a URL or query string — URLs end up in server logs and browser history.
    * Commit it. Check your `.env` is in `.gitignore` before the first push.
  </Card>
</Columns>

Anyone holding your key can send WhatsApp messages as you, from your numbers, to anyone. Treat it exactly like a password.

## One key drives all your numbers

A key identifies your **account**, not a number. The same key can send from every number on your account — you choose which one per request, with `instance_id`:

```json theme={null}
{ "instance_id": "3f9c1a2b-...", "to": "919876543210", "text": "From Sales" }
```

```json theme={null}
{ "instance_id": "a1b7c3d9-...", "to": "919876543210", "text": "From Support" }
```

Same key, same header, different number sending. See [One key, many numbers](/quickstart#one-key-many-numbers).

You can still create **several keys** on one account if you want to — one per application, say — so that revoking a key only affects the system it belongs to. Every key on the account can reach every number on the account; keys are not scoped to individual numbers.

## Revoking a key

On the **API Keys** screen, revoke the key you no longer want.

Revocation takes effect on the very next request: the key is checked against the database every time it is used, so there is no cache to wait out. A revoked key returns `401 invalid_api_key` — exactly the same answer an entirely made-up key gets, so nobody can probe which keys once existed.

## Rotating without downtime

Because an account can hold several live keys at once, rotation needs no maintenance window:

<Steps>
  <Step title="Create the new key">
    Label it clearly, for example `billing-server-2026-08`.
  </Step>

  <Step title="Deploy it">
    Update the environment variable and restart. Both keys work at this point.
  </Step>

  <Step title="Confirm it is in use">
    The key list shows **last used** for each key. Wait until the new key shows recent activity and the old one has gone quiet.
  </Step>

  <Step title="Revoke the old key">
    Now revoke it. If something was still using it, that system starts failing with `401 invalid_api_key` — which is exactly the signal you want.
  </Step>
</Steps>

## What else can block a valid key

A key can be perfectly valid and still be refused, because authentication is only the first gate. In order, a request is checked for:

| Check                                         | Failure                                                  |
| --------------------------------------------- | -------------------------------------------------------- |
| Is the key known and not revoked?             | `401 invalid_api_key`                                    |
| Is the account active?                        | `403 account_suspended`                                  |
| Is the account's access period still running? | `403 access_expired`                                     |
| Is there a usable plan?                       | `402 no_active_subscription`, `402 subscription_expired` |
| Are you inside your per-minute limit?         | `429 rate_limit_exceeded`                                |

Each is explained on the [Errors](/errors) page.

## Testing a key

The cheapest way to prove a key works is to list your numbers. It sends nothing and costs nothing:

```bash theme={null}
curl -i https://api.bulkneo.com/v1/instances \
  -H "apikey: YOUR_API_KEY"
```

A `200` means the key is good. A `401` means it is not.
