# Authentication

> API keys, how to send them, and the scopes that decide what a key may do.



Every request except the OpenAPI document (`GET /api/v1/openapi.json`) needs an API key, sent as a bearer token:

```bash
curl -H "Authorization: Bearer aur_YOUR_KEY" \
  https://your-site.awrora.app/api/v1/me
```

## API keys

Keys are created in Awrora under <Path>Settings → Apps → For developers</Path> with **New API key**. A key looks like `aur_` followed by 40 characters, and it is **shown once, when it is created**. Awrora stores only a hash of it plus the first 12 characters for display, so a lost key cannot be recovered — create a new one and revoke the old.

<Callout type="warning" title="Treat a key like a password">
  Keep it server side, out of source control, out of browsers and mobile apps. Anyone holding the key can act as the organisation within the key's scopes.
</Callout>

An organisation can hold at most **20 active keys**; revoke one you no longer use before creating another. A revoked key answers `401` `api_key_revoked` immediately.

[`GET /v1/me`](/api-reference/account/get-me) is the auth test. It needs no scope and returns the organisation the key belongs to, the key's name, its display prefix and its scopes.

### The 401 codes

| Code              | What it means                     | What to do                            |
| ----------------- | --------------------------------- | ------------------------------------- |
| `api_key_missing` | No `Authorization: Bearer` header | Send the header                       |
| `api_key_invalid` | Not a key we know                 | Check for a truncated or mistyped key |
| `api_key_revoked` | The merchant revoked it           | **Stop retrying.** Ask for a new key  |
| `api_key_expired` | The key passed its expiry         | **Stop retrying.** Ask for a new key  |

## Scopes

A key carries a list of scopes. A new key is created with every `:read` scope selected; write scopes and `webhooks:manage` are opt-in. Missing one is `403` `insufficient_scope`, and the response names the scope you needed in `required_scope`:

```json
{
  "error": "This API key is missing the required scope \"bookings:write\".",
  "code": "insufficient_scope",
  "required_scope": "bookings:write"
}
```

| Scope               | Grants                                                                                                                                                   |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `experiences:read`  | List and get experiences                                                                                                                                 |
| `availability:read` | Departures with live seat counts (`GET /v1/availability`)                                                                                                |
| `bookings:read`     | List and get bookings                                                                                                                                    |
| `bookings:write`    | [Create](/api-reference/bookings/create-booking) and [cancel](/api-reference/bookings/cancel-booking) bookings — a cancellation **may refund** the guest |
| `customers:read`    | List and get customers                                                                                                                                   |
| `customers:write`   | Create and update customers                                                                                                                              |
| `gift_cards:read`   | List and get gift cards                                                                                                                                  |
| `webhooks:manage`   | Everything under `/v1/webhooks`. Subscribing an endpoint to an event type also needs that event's read scope, as for `GET /v1/events`                    |

`GET /v1/me` needs no scope. [`GET /v1/events`](/api-reference/events/list-events) needs the read scope for the **resource behind the event**, because the event carries that whole resource: a key with only `bookings:read` sees `booking.*` events and nothing else. Asking for a `type` you lack the scope for is `403` `insufficient_scope` rather than an empty page — an empty page would read as "it never happened".

<Callout type="danger" title="bookings:write can move money">
  Cancelling a booking through the API runs the same cancellation as a staff member clicking *Cancel* in Awrora, including the organisation's **refund policy**: a paid booking cancelled inside the policy's window is refunded to the guest's card automatically. There is no separate refund scope — treat a key with `bookings:write` as one that can issue refunds.
</Callout>

`bookings:write` also unlocks the booking's `manage_url`. The link carries a bearer token, so read-only keys — and every webhook and event payload — get `null` instead.

Ask for the narrowest set of scopes that does the job, and use one key per integration. Scopes are the only thing standing between a leaked read-only key and a leaked key that can cancel bookings.

## If a key leaks

Revoke it under <Path>Settings → Apps → For developers</Path> and create a new one. When you contact support, quote the key's **prefix** (the first twelve characters shown in Awrora) — never the whole key.
