# Publisher API

With the **Publisher API** you publish events from your website, plugin or system as [subscribe calendars](https://docs.kalender-sync.de/en/abo-kalender/). You write events with `PUT` under your own id. Kalender Sync turns them into an ICS feed, a subscribe page and an embeddable button.

:::tip[OpenAPI 3.1]
The machine-readable description of every endpoint is public, no key needed:
**[https://api.kalender-sync.de/v1/openapi.json](https://api.kalender-sync.de/v1/openapi.json)**.
Load it into Postman, Insomnia or a code generator, or hand it to your AI agent.
:::

## At a glance

| | |
|---|---|
| Base URL | `https://api.kalender-sync.de/v1` |
| Format | JSON in and out, UTF-8, HTTPS only |
| Authentication | `Authorization: Bearer ks_live_…` or `ks_test_…` → [Authentication](https://docs.kalender-sync.de/en/api/authentication/) |
| Errors | `application/problem+json` (RFC 9457) with a stable `code` → [Errors](https://docs.kalender-sync.de/en/api/errors/) |
| Rate limit | 120 requests per minute per key → [Limits](https://docs.kalender-sync.de/en/api/limits/) |
| Sandbox | `ks_test_` keys only see sandbox calendars, on the same API |

## Endpoints

All paths relative to the base URL.

| Method and path | Purpose |
|---|---|
| `GET /me` | Check the key: account, mode, scopes |
| `POST /publisher/calendars` | Create a calendar |
| `GET /publisher/calendars` | List calendars |
| `GET /publisher/calendars/{calendar_id}` | Get a calendar, with feed URL and subscribe links |
| `PATCH /publisher/calendars/{calendar_id}` | Update a calendar |
| `DELETE /publisher/calendars/{calendar_id}` | Delete a calendar |
| `PUT /publisher/calendars/{calendar_id}/images/{logo\|banner}` | Upload the logo or banner |
| `DELETE /publisher/calendars/{calendar_id}/images/{logo\|banner}` | Remove the logo or banner |
| `PUT /publisher/calendars/{calendar_id}/events/{external_id}` | Create or update an event |
| `GET /publisher/calendars/{calendar_id}/events/{external_id}` | Get an event |
| `DELETE /publisher/calendars/{calendar_id}/events/{external_id}` | Delete an event |
| `GET /publisher/calendars/{calendar_id}/events` | List events (cursor, `updated_since`) |
| `POST /publisher/calendars/{calendar_id}/events/batch` | Up to 100 changes in one call |
| `POST /publisher/calendars/{calendar_id}/syncs` | Start a full sync |
| `POST /publisher/calendars/{calendar_id}/syncs/{sync_id}/complete` | Finish a full sync |
| `GET /publisher/calendars/{calendar_id}/stats` | Subscribers and clicks |

## Principles

- **Your ids, not ours.** You address events by your own `external_id`, for example the WordPress post ID. You don't need to store any id from us except the calendar id.
- **Safe to retry.** `PUT` and `DELETE` on events are idempotent. An unchanged event answers `"changed": false`, and no calendar app has to reload. `POST /publisher/calendars` accepts an `Idempotency-Key` header. → [Idempotency](https://docs.kalender-sync.de/en/api/idempotency/)
- **Unknown fields are refused.** A typo in a field name leads to `400 invalid_request` instead of being silently ignored.
- **Additive changes.** `/v1` only grows: new fields, new optional parameters, new error codes. Ignore response fields you don't know.
- **Subscribers get changes on the next fetch.** Calendar apps fetch the feed on their own schedule. → [How fast do changes arrive?](https://docs.kalender-sync.de/en/abo-kalender/faq/#how-fast-do-changes-reach-subscribers)

## Get started

- [Quickstart](https://docs.kalender-sync.de/en/api/quickstart/): from key to subscribable calendar in five curl calls
- [Events and recurrence](https://docs.kalender-sync.de/en/api/events/): fields, RRULE, mapping of typical plugin fields
- [Button and ks:subscribe](https://docs.kalender-sync.de/en/api/button/): embed the subscribe button
- [For AI agents](https://docs.kalender-sync.de/en/api/ai-agents/): copy-prompt and OpenAPI for your agent