# Keys and requests

How to pass the key, how a test key differs from a live one, and the shape of the data the API takes and returns.

## API key {#keys}

Every request carries the organization's key in a header:

```http
Authorization: Bearer bt_live_…
```

| Prefix | Mode | What the key sees |
|---|---|---|
| `bt_test_` | test | test events, orders, tickets and devices only |
| `bt_live_` | live | live ones only |

The organization owner creates and revokes keys in the dashboard under **интеграция → ключи API** (Integration → API keys). A key works in the mode the dashboard was in when the key was created. You see the full key once. We store only its hash, so a lost key can't be recovered: revoke it and create a new one.

- A revoked key stops working within 5 seconds.
- A suspended organization's key doesn't work.
- With no key, a wrong key or a revoked key the response is `401 unauthorized`.

> The key opens every event and ticket of the organization in its mode. Keep it on your server. It has no place in page code, a mobile app or a public repository.

## Base URL {#host}

The API base URL is `https://api.budu.events/v1`. Requests are accepted over HTTPS only.

## Organization and mode {#scope}

The key sets the organization and the mode. The `org_id` and `livemode` fields in a request body have no effect. Another organization's event, order or ticket, and an object from the other mode, look as if they don't exist: `404 not_found`.

## What a key can do {#available}

| Section | What it does |
|---|---|
| [Events](https://budu.events/en/docs/events/) | create, update, publish, availability, cover |
| [Assigned seating](https://budu.events/en/docs/seats/) | upload the seat map, list seats |
| [Orders](https://budu.events/en/docs/orders/) | an order after payment, a reservation during payment |
| [Tickets](https://budu.events/en/docs/tickets/) | find, cancel, reissue, resend the email, export |
| [Issue without payment](https://budu.events/en/docs/issue/) | issue tickets by hand, upload a guest list |
| [Door staff](https://budu.events/en/docs/devices/) | pair a door staff phone, check-in report |

API keys, branding, test recipients, the activity log and an event's live numbers are dashboard-only. A key gets `403 forbidden` with `details.reason: "session_only"` on those requests.

## Data format {#format}

- The request body is JSON in UTF-8, up to 1 MB. Files (seats, guest list, cover) go as the request body as they are, with their own `Content-Type`.
- Field names are `snake_case`. An unknown field in a request body gives `400 invalid_request`, so a typo in a field name never slips through unnoticed. The exception is devices (`POST /v1/devices/invites`, `PATCH /v1/devices/{id}`), which ignore extra fields.
- A response has every field of the object, even empty ones: string `""`, number `0`, list `[]`, nested object `null`.
- IDs are UUID strings: `"0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60"`.
- Times are RFC 3339 strings. Responses are always in UTC: `"2026-12-12T16:00:00Z"`. In requests you can give an offset: `"2026-12-12T19:00:00+03:00"`.
- `*_ms` fields are Unix milliseconds. `"0"` means "never": `"checked_in_at_ms": "0"` on a ticket that hasn't checked in.
- 64-bit numbers (`price_minor` and every `*_ms`) come back **as strings**: `"price_minor": "250000"`. That's how protobuf writes them to JSON. In requests a number or a string both work.
- Money is in the currency's minor units: `250000` kopecks means 2,500 ₽. The currency is a three-letter code, `RUB` by default.

## Headers {#headers}

| Header | Where | Why |
|---|---|---|
| `Authorization` | request | `Bearer` and the API key |
| `Idempotency-Key` | request, `POST` and `DELETE` | safe retries, see [idempotency](https://budu.events/en/docs/idempotency/) |
| `Content-Type` | request with a body | `application/json` for JSON, `text/csv` for seats and guests, `image/jpeg`, `image/png` or `image/webp` for the cover |
| `X-Request-Id` | response | the request's number in our logs. Quote it when you write to us about a problem |
| `Idempotent-Replayed` | response | `true` when this is the stored response to a retry |
| `Retry-After` | `429` response | how many seconds to wait before retrying |
