# Authentication

> API keys for Reggio Digital: how to get one, what each ability allows, limiting a key to a domain, and replacing a key without downtime.

Source: https://reggiodigital.com/developers/authentication

## API keys

Every call carries a key in the `Authorization` header:

```
Authorization: Bearer rgo_your_key_here
```

Keys start with `rgo_`. A key belongs to the business's account, not to the person who created it, so it keeps working when that person leaves the account. Keys that send email or work with subscribers are made under [Connected apps](https://reggiodigital.com/email/api/keys), by anyone on the account who has been given access to Email. Keys that read the account are made under [API keys](https://reggiodigital.com/settings/api-keys); see the [account API](https://reggiodigital.com/developers/account).

A missing, malformed or revoked key gets a `401` with the error type `unauthorized`.

## Checking a key

`GET /api/v1/me`

Any working key can make this call, whatever abilities it has, so it is the quickest way to confirm a key is in place before you build anything on top of it.

cURL:

```bash
curl https://reggiodigital.com/api/v1/me \
  -H "Authorization: Bearer $REGGIO_API_KEY"
```

Laravel:

```php
use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.reggio.key'))
    ->acceptJson()
    ->get('https://reggiodigital.com/api/v1/me');

if ($response->failed()) {
    throw new RuntimeException($response->json('error.message'));
}

$body = $response->json();
```

PHP:

```php
$ch = curl_init('https://reggiodigital.com/api/v1/me');

curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer '.getenv('REGGIO_API_KEY'),
    ],
]);

$body = json_decode((string) curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($status >= 400) {
    throw new RuntimeException($body['error']['message']);
}
```

Node.js:

```js
const response = await fetch("https://reggiodigital.com/api/v1/me", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.REGGIO_API_KEY}`,
  },
});

const body = await response.json();

if (!response.ok) {
  throw new Error(body.error.message);
}
```

It answers with the account the key belongs to and what the key may do:

```json
{
  "account": {
    "id": 812,
    "name": "Glass Jug Beer Lab"
  },
  "key": {
    "name": "Order system",
    "prefix": "rgo_AbCd1234",
    "abilities": ["email:send"],
    "sending_domains": ["email.yourdomain.com"],
    "created_at": "2026-09-11T14:02:10+00:00",
    "last_used_at": "2026-09-25T09:30:00+00:00",
    "expires_at": null
  }
}
```

`sending_domains` is `null` when the key can send from every verified domain on the account. `expires_at` is `null` for a key with no end date, and set to the moment it stops when it has been [replaced](#replacing-a-key) with an overlap. The call works on an account that has not bought anything yet, and it never returns the key itself.

## Keep the key on your server

A key can send email as the business. Treat it like a password:

- Store it in an environment variable or your platform's secret store.
- Never put it in JavaScript that runs in a browser, in a mobile app bundle, or in a public repository.
- A signup form on a website should post to your own server, and your server calls us.

If a key does leak, replace it straight away (see below). The old one can be stopped the same second.

## Abilities

A key can only do what it was given when it was connected. Give each application the least it needs.

| Ability | Lets the key |
| --- | --- |
| `email:send` | Send, schedule, reschedule, cancel and look up transactional email. |
| `newsletter:read` | Look up lists, custom fields and contacts. Cannot change anything. |
| `newsletter:manage` | Everything `newsletter:read` can, plus add, update and unsubscribe contacts. |
| `websites:read` | Read websites, their backups and their monthly visits. |
| `tickets:read` | Read every support request on the account and its replies. |
| `domains:read` | Read domains and the DNS records on each zone. |
| `billing:read` | Read invoices and the plans and add-ons being paid for. |

A key without the ability a call needs gets a `403` with the error type `forbidden`. A newsletter key cannot send email, and an email key cannot touch the subscriber list.

The business also needs the product the call belongs to. A valid key on an account with no transactional email plan gets a `402` with `no_plan` from the email endpoints, and the same goes for the newsletter endpoints.

## Limiting a key to sending domains

When you connect an application you can limit its key to one or more of the account's verified sending domains. A limited key:

- can only send from addresses on those domains, and gets a `403` with `send_rejected` for any other from address;
- can only look up, reschedule or cancel messages sent from those domains. Anything else reads as a `404`.

Use this when one account runs several brands, or when a contractor's application should only ever send as one of them.

## One key per application

Connect each application separately rather than sharing one key. Each gets its own name in the dashboard, its own last-used time and its own entries in the [API log](https://reggiodigital.com/email/api/log), and you can replace or switch off one without touching the others.

## Replacing a key

Replacing a key in [Connected apps](https://reggiodigital.com/email/api/keys) creates a new key with the same name, abilities and domain limits, and lets you choose how long the old one keeps working:

- stop straight away, for a key you believe has leaked;
- keep working for an hour, a day or a week, so you can deploy the new key without a gap.

During the overlap both keys are accepted. Scheduled messages sent with the old key move to the new one, so they still go out on time after the old key stops.
