# Authentication

Every request carries an API key as a bearer token:

```
Authorization: Bearer ks_live_…
```

Without the header the API answers `401` [`missing_api_key`](https://docs.kalender-sync.de/en/api/errors/missing_api_key/); with a wrong, revoked or expired key `401` [`invalid_api_key`](https://docs.kalender-sync.de/en/api/errors/invalid_api_key/). This also applies to unknown paths: without a valid key you get `401`, not `404`, so the API does not reveal which paths exist.

Only `GET /v1/openapi.json` is public and needs no key.

## Create keys

You create keys in the [app](https://app.kalender-sync.de/) under **Settings › Developer / API**.

- **Sandbox key** (`ks_test_…`): on every plan. Only sees sandbox calendars.
- **Live key** (`ks_live_…`): Basic and up. Only sees live calendars.

The key is shown **exactly once**. We only store a hash of it. If you lose it, create a new one. An account can have up to **10 active keys**, so you can rotate keys without downtime: create a new key, put it in the plugin, revoke the old one.

## Key format

```
ks_live_<32 characters>_<6-character checksum>
ks_test_<32 characters>_<6-character checksum>
```

The checksum at the end lets secret scanners recognize a leaked key and lets us reject a mistyped key without a database lookup. Access keys for the MCP server (`ksa_…`) do not work here.

## Check a key: `GET /v1/me`

Ideal for a "Test connection" button in your plugin.

```bash
curl https://api.kalender-sync.de/v1/me -H "Authorization: Bearer $KS_KEY"
```

```json
{
  "account_id": "…",
  "key": {
    "id": "…",
    "name": "My website",
    "mode": "live",
    "scopes": ["publisher:read", "publisher:write", "publisher:stats"],
    "calendar_ids": null
  }
}
```

| Field | Meaning |
|---|---|
| `mode` | `live` or `test` (sandbox). |
| `scopes` | What the key may do: `publisher:read` (read), `publisher:write` (create, update, delete), `publisher:stats` (statistics). Keys from the settings have all three. |
| `calendar_ids` | `null` if the key sees every calendar of the account. Otherwise the list of calendars it is limited to. A limited key cannot create new calendars. |

If a scope is missing the API answers `403` [`insufficient_scope`](https://docs.kalender-sync.de/en/api/errors/insufficient_scope/).

## Sandbox and live

Both kinds of key talk to the same API at the same URL. A sandbox key only sees sandbox calendars, a live key only live calendars. A calendar of the other mode answers `404` [`calendar_not_found`](https://docs.kalender-sync.de/en/api/errors/calendar_not_found/).

| | Sandbox | Live |
|---|---|---|
| Calendars per account | 3 | depends on the plan: Basic 1, Professional 10 |
| Events per calendar | 500 | 5,000 |
| Name in the feed | prefixed with `[TEST]` | as created |
| Subscribe page | with a "Test calendar" note | without note |

## Keep the key safe

- Store the key **server side** only, for example as a WordPress option that is not autoloaded. Never in HTML, your page's JavaScript or a public repository.
- Don't write it to logs or error messages.
- The button on your website needs **no** key, only the calendar id.

## Revoke a key

Under **Settings › Developer / API**, click **Revoke**. It takes effect on the next request; anything using the key then gets `401 invalid_api_key`. Existing calendars, feeds and subscriptions stay as they are.