# Authentication

> Organisation API keys, their scopes, what a key may do and how to rotate it.

Every operation of the API needs an API key of your organisation, except the public passport reads (tag
`public`), which need none.

## Send the key

Send the key in the `Authorization` header with the `Bearer` scheme:

```http title="Header"
Authorization: Bearer bp_live_...
```

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/registry" \
  -H "Authorization: Bearer $BATTERYPASS_API_KEY"
```

```ts title="TypeScript"
const res = await fetch(`${process.env.BATTERYPASS_URL}/api/v1/registry`, {
  headers: { Authorization: `Bearer ${process.env.BATTERYPASS_API_KEY}` },
});
console.log(res.status, await res.json());
```

```python title="Python"
import os

import requests

res = requests.get(
    f"{os.environ['BATTERYPASS_URL']}/api/v1/registry",
    headers={"Authorization": f"Bearer {os.environ['BATTERYPASS_API_KEY']}"},
    timeout=30,
)
print(res.status_code, res.json())
```

```go title="Go"
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

func main() {
	req, err := http.NewRequest(http.MethodGet, os.Getenv("BATTERYPASS_URL")+"/api/v1/registry", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("BATTERYPASS_API_KEY"))
	res, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status, string(body))
}
```

The API reads keys only from this header. Never put a key into a URL: query strings and paths end up in
logs, browser histories and proxies.

## Organisation keys

- A key belongs to the **organisation**, not to a member. It keeps working when the member who created it
  leaves.
- **Owners** create and revoke keys in the portal under **Settings → API keys**. Key management is not part
  of the API.
- The secret is `bp_live_` followed by 43 characters. It is shown **once**, when the key is created.
  BatteryPass stores only a SHA-256 hash and shows the first 12 characters as a prefix so you can tell keys
  apart.
- An organisation has at most **25 active keys**. A revoked key stays listed with its revocation time and
  never works again.
- Keys have **no expiry date**. You end a key by revoking it (see [Rotate a key](#rotate-a-key)).

## Scopes

A key has one or more scopes. Each operation names the scope it needs in `x-api-key-scope` in the contract and
on its page in the [API reference](https://passport.kabin.sh/docs/en/api-reference).

| Scope       | Allows                                                                                                                                                                                                                             | Operations                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read`      | Reading what a viewer of the organisation sees: models, versions, units, values with provenance and completeness, evidence, facilities, passports with versions, diffs and exports, lifecycle events, condition data, the registry | Every operation with `x-api-key-scope: read`                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `condition` | Reporting condition data and lifecycle events                                                                                                                                                                                      | [`updateCondition`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/update-condition), [`createLifecycleEvent`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/create-lifecycle-event)                                                                                                                                                                                                                                                                  |
| `import`    | Staging spreadsheet imports of units: upload, map, validate and follow them; members commit and approve them in the portal (ADR-026)                                                                                               | [`createImport`](https://passport.kabin.sh/docs/en/api-reference/imports/create-import), [`updateImportMapping`](https://passport.kabin.sh/docs/en/api-reference/imports/update-import-mapping), [`validateImport`](https://passport.kabin.sh/docs/en/api-reference/imports/validate-import), [`getImport`](https://passport.kabin.sh/docs/en/api-reference/imports/get-import), [`listImports`](https://passport.kabin.sh/docs/en/api-reference/imports/list-imports) |

The scopes do not include each other. A battery management system that reports measurements and also reads
the current condition with [`getCondition`](https://passport.kabin.sh/docs/en/api-reference/lifecycle/get-condition) needs both.

To list every operation with its scope, read the contract of your deployment:

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/openapi.json" \
  | jq -r '.paths[][] | objects | select(has("operationId")) | "\(.operationId)\t\(.["x-api-key-scope"] // "public")"'
```

## What a key may do

A key acts in its organisation with the rights of a **viewer**. Where an operation's description names a role
("any member", "editors and above"), a key counts as a viewer. The exceptions: the two operations of the
`condition` scope accept a key with that scope, and the import operations of the `import` scope accept a key with
that one.

A key never:

- creates or changes declared data (models, units, values, documents);
- reviews, approves, publishes or withdraws anything, and never commits or approves an import, also one it staged;
- manages members, API keys or organisation settings.

These stay with members in the portal, so the four-eyes principle and the publication checks keep their
meaning. What a key reports is recorded with the source `api`, and the lifecycle event of the report names
the key in its `api_key` member.

## Session and key together

A request that carries both a portal session cookie and an API key is refused with `401`. This can happen when
you try requests in a browser in which you are signed in to the portal. Use a client without cookies, such as
`curl` or your server code.

## 401 or 403

| Status | `code`            | Cause                                                                                                           | What to do                                                                                  |
| ------ | ----------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `401`  | `unauthenticated` | The header is missing or malformed, the key is unknown or revoked, or the request also carries a session cookie | Check the header and the environment variable; ask an owner for a new key if it was revoked |
| `403`  | `forbidden`       | The key lacks the operation's scope, or the action needs more than a viewer's rights                            | Use a key with the right scope; actions beyond a viewer's rights are done in the portal     |
| `429`  | `rate_limited`    | Too many attempts with unknown or revoked keys from your client address                                         | Stop and fix the key; see [Rate limits](https://passport.kabin.sh/docs/en/rate-limits)    |

> [!WARNING]
> Do not retry a `401` in a loop. Requests with unknown or revoked keys are limited to 30 attempts per 15
> minutes per client address; after that, further attempts with such a key get `429` instead of `401` until
> the window ends.

## Rotate a key

Keys do not expire, so rotate them yourself: on a regular schedule, at once when a key may have leaked, and
when someone who knew it leaves.

1. **Create the new key**

   An owner creates a new key with the same scopes in **Settings → API keys**.

2. **Deploy it**

   Put the new secret into your secret store and restart or reconfigure the system that uses it.

3. **Check that the old key is idle**

   In the portal, the old key's "Last used" time stops changing. It is updated at most once a minute.

4. **Revoke the old key**

   Revoking takes effect at once and cannot be undone.

## Keep keys secret

- Use keys only in server-side systems, never in browser code, mobile apps or public repositories.
- Create one key per system, so you can revoke one without stopping the others.
- Give each key only the scopes it needs.
- The prefix `bp_live_` makes leaked keys easy to find for secret scanners.

More in [Secure key handling](https://passport.kabin.sh/docs/en/guides/secure-key-handling) and [Security](https://passport.kabin.sh/docs/en/general/security).
