# Authentication | Rijwind docs

# Authentication

Every Rijwind API request requires a Bearer key.

```http
Authorization: Bearer rw_live_•••YOUR_KEY•••
```

Keys start with `rw_live_` (production) or `rw_test_` (sandbox /
staging). Both flow through the same endpoints and the same per-user
quota — the prefix is a convenience for telling them apart in logs.

## Where keys live

- A **user** has many **projects**.
- A **project** has many **API keys**.
- **Quota is on the user**, aggregated across every key. Issuing more
  keys does not grant more quota.

Manage all of this from
[`/dashboard/projects`](https://rijwind.com/dashboard/projects).

The plaintext value of a key is shown **once**, at creation. Lose it
and your only option is to issue a new one — we only store the SHA-256
hash.

## Origin whitelist (browser-side keys)

Each project carries an **allowed-origins** list. When set, the API
checks the request's `Origin` (or `Referer`, falling back) and rejects
calls from anywhere else with `403 Forbidden`.

- Origins are matched against `scheme://host[:port]` — path and query
  are ignored.
- Subdomain wildcards are supported: `https://*.example.com` matches
  `https://app.example.com` but **not** the bare apex `https://example.com`.
  Add both if you need both.
- An empty list means "allow any origin" — fine for server-side keys
  that never touch a browser.

The recommended setup:

| Use case             | Allowed origins                         |
| -------------------- | --------------------------------------- |
| Your production site | `https://yourapp.com`, `https://*.yourapp.com` |
| Staging / preview    | `https://staging.yourapp.com`, `https://*.preview.yourapp.com` |
| Server-side scripts  | Leave empty, store the key in your secrets manager |
| Mobile apps          | Leave empty (no `Origin` header) and rotate the key periodically |

## Scoped keys

Each key carries an optional **scope list** that narrows it to a subset
of the API. A request to an endpoint outside the key's scopes returns
`403 scope_denied` without charging quota.

Three scopes are available in v1:

| Scope     | Endpoints                                                                 |
| --------- | ------------------------------------------------------------------------- |
| `tiles`   | `GET /tiles/v1/token`                                                     |
| `geocode` | `GET /search/geocode/v1/forward`, `/autocomplete`, `/reverse`                     |
| `routing` | `POST /directions/v1`, `/directions-matrix/v1`, `/isochrone/v1`                           |
| `static`  | `GET /styles/v1/{style}/static/…`                                         |

Leave the scope list empty to issue a full-access key (the default for
new keys and the only mode for keys issued before scopes existed).
Recommended patterns:

| Use case                          | Scopes                              |
| --------------------------------- | ----------------------------------- |
| Public-facing map widget          | `tiles`                             |
| Address autocomplete in a form    | `geocode`                           |
| Server-side route planner backend | `geocode`, `routing`                |
| Internal data-pipeline service    | All scopes (leave empty)            |

Narrow keys are a defence-in-depth measure: a leaked `tiles`-only key
can show maps from your domain, but can't be used to scrape addresses
or compute a million routes on your bill.

## Key transport

The Bearer header is the only path we document — it is the right answer
for browser, server, and CLI use cases. For convenience, the API also
accepts a `?key=` query parameter; we recommend against it because keys
in query strings end up in proxy logs and browser history.

## Revoking a key

In the dashboard, open the project and click **Revoke** on the key
row. Within a second or two the Redis mirror is updated and the next
request with that key returns `403 key_revoked`. Revocation cannot be
undone — issue a new key in its place.
