API reference
djstrich API
Booking platform API for DJs, promoters, and booking agencies.
Version 1.0.0 · Base URL https://djstrich.de · 8 operations
Getting started
- As an owner of a promoter organisation, open your promoter profile and find API access. Create a key — it is shown once and only its hash is stored, so copy it immediately.
-
Send it as
Authorization: Bearer djs_live_…on any/api/v1/**request. The key determines which organisation you are acting for; there is no organisation parameter anywhere in v1. -
Discover ids:
GET /api/v1/events, thenGET /api/v1/events/{eventId}/slotsfor slot ids, andGET /api/v1/djsfor DJ profile ids. -
Offer a slot to a DJ, or invite one who is not on djstrich yet. Then either register a
webhook or poll
GET /api/v1/bookings?updated_since=….
curl -X POST https://djstrich.de/api/v1/slots/$SLOT_ID/book \
-H "Authorization: Bearer $DJSTRICH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dj_profile_id":"...","message":"Friday, 02:00, main room. 90 minutes."}' Two ways to put a DJ on a slot
The DJ already has a djstrich profile →
POST /api/v1/slots/{slotId}/book. This creates a
booking request and notifies them. It does not assign them: the slot stays open
until they accept. That is intentional — a promoter cannot put a DJ's name on a public
line-up without their consent, through the API any more than through the app.
The DJ is not on djstrich →
POST /api/v1/djs/invite. This returns a claimable link and
tags your slots with their name. It does not create a profile; the profile
exists once they open the link and sign up, at which point their slots attach
automatically and an invite.accepted webhook fires.
Authentication
apiKeyAuth
http
Promoter API key, sent as `Authorization: Bearer djs_live_…`. Org-scoped and created by an organisation owner under Promoter → profile → API access. Only a SHA-256 hash is stored, so the token is shown exactly once at creation. Valid on `/api/v1/**` only; it is not accepted anywhere else on the site.
Errors
Every /api/v1/** error returns
{"error": {"code", "message"}}. Branch on
code, which is stable; message
is human-readable English and may be reworded. Responses are never translated — a machine
API should not change its payload based on who is calling it.
A resource that belongs to another organisation returns 404, not 403. The two are deliberately indistinguishable so the API never confirms that another promoter's records exist.
Outbound webhooks
Register an endpoint with POST /api/v1/webhooks (or in the
dashboard). We POST a signed JSON envelope to it whenever a booking changes. Deliveries
are queued and retried with backoff (1 min, 5 min, 30 min, 2 h, 6 h; five attempts).
Expect delivery within about five minutes of the event, not instantly.
Exactly one event fires per transition — the most specific one that applies.
booking.status_changed is the catch-all for transitions
with no more specific type, not a duplicate of
booking.confirmed. Subscribing to both does not double up.
Respond 2xx quickly; anything else counts as a failure, and redirects are never followed. An endpoint that fails ten times in a row is disabled until you re-enable it from the dashboard.
Verify every delivery. The
X-DJStrich-Signature header is
t=<unix seconds>,v1=<hex>, an HMAC-SHA256 over
`{t}.{raw body}` keyed with your endpoint
secret. Verify against the raw body bytes, before parsing.
// Node — verify X-DJStrich-Signature: t=<unix seconds>,v1=<hex>
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map((kv) => {
const i = kv.indexOf('=')
return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()]
})
)
// Reject replays: the timestamp must be within 5 minutes of now.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
const expected = createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(parts.v1 ?? '')
return a.length === b.length && timingSafeEqual(a, b)
} Envelope
- id
- string
- Unique per event. Shared across endpoints, so two receivers can dedupe.
- type
- string (booking.confirmed | booking.declined | booking.cancelled | booking.status_changed | booking.requested | invite.accepted | event.lineup_complete | event.is_beginning | event.has_ended)
- created_at
- string
- data
- object
v1
Promoter API v1 — the stable, versioned surface for driving a promoter organisation from your own systems. Authenticated with an API key; every request is scoped to the organisation that key belongs to.
GET /api/v1/bookings List booking requests (polling feed)
Every booking request of your organisation, most recently updated first. This is the polling alternative to webhooks: keep the highest `updated_at` you have seen and pass it as `updated_since` on the next call, then follow `next_cursor` until it is null. Pagination is keyset-based on `(updated_at, id)`, so rows cannot be skipped or repeated when data changes between pages.
Auth: apiKeyAuth
Parameters
-
updated_since· query ISO 8601 timestamp; only rows updated at or after this -
status· query Filter to one booking status (e.g. confirmed, cancelled) -
limit· query Page size, default 50, max 100 -
cursor· query The `next_cursor` from the previous response
Responses
-
200A page of booking requests -
400Invalid cursor or updated_since -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
429Too many requests. Retry after the number of seconds in `Retry-After`.
GET /api/v1/djs Search DJ profiles
Search the public DJ catalogue by name, city, country, genre and availability date. Returns approved, publicly listed profiles only. Use the `id` of a result as `dj_profile_id` when booking a slot.
Auth: apiKeyAuth
Parameters
-
q· query Free-text artist name search -
city· query Home-base city, or "worldwide" -
country· query ISO country code (DE, AT, CH, GB, NL, TH) -
genres[]· query Repeatable genre filter -
date· query Availability date (YYYY-MM-DD). Excludes DJs who have blocked that day. -
sort· query -
limit· query Page size, default 24, max 48 -
cursor· query Pagination cursor from the previous response
Responses
-
200Matching DJ profiles -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
429Too many requests. Retry after the number of seconds in `Retry-After`.
POST /api/v1/djs/invite Invite a DJ who is not on djstrich yet
Creates a claimable invite link and tags the given slots with the DJ's name. This does NOT create a DJ profile. The profile comes into existence when the invitee opens the link and signs up, at which point the slots they were tagged on are attached to their new profile automatically and an `invite.accepted` webhook fires. Until then the slots show the name as an external tag. All slots must belong to your organisation; otherwise the call returns 404.
Auth: apiKeyAuth
Request body (required)
-
name· string · required The DJ's name as it should appear on the line-up -
slot_ids· array · required 1–50 slot ids from GET /api/v1/events/{eventId}/slots -
locale· string (de | en) Language of the invite landing page. Defaults to English.
{
"name": "Nadia Kraft",
"slot_ids": [
"3c1e0b9a-7c1e-4f0a-9c1e-0b9a7c1e4f0a"
],
"locale": "de"
} Responses
-
201Invite created -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
404The resource does not exist, or belongs to another organisation. These are deliberately indistinguishable — the API never confirms that another promoter's records exist. -
422Missing name, or slot_ids empty / longer than 50 -
429Too many requests. Retry after the number of seconds in `Retry-After`.
GET /api/v1/events List your organisation's events
Every event belonging to the organisation the API key was issued for, newest first. There is no organisation parameter — the key determines the scope, so a key can never read another promoter's events.
Auth: apiKeyAuth
Responses
-
200Events for the key's organisation -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
429Too many requests. Retry after the number of seconds in `Retry-After`.
GET /api/v1/events/{eventId}/slots List an event's line-up slots
The set slots of one event, including whether each is already filled and whether it is currently an open call. This is how you discover the `slot_id` values that `POST /api/v1/slots/{slotId}/book` and `POST /api/v1/djs/invite` take. An event belonging to a different organisation returns 404, not 403 — the API does not confirm that another promoter's records exist.
Auth: apiKeyAuth
Parameters
-
eventId· path · required
Responses
-
200Event summary plus its slots -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
404The resource does not exist, or belongs to another organisation. These are deliberately indistinguishable — the API never confirms that another promoter's records exist. -
429Too many requests. Retry after the number of seconds in `Retry-After`.
POST /api/v1/events/{eventId}/slots Create line-up slots on an event
Adds 1–50 new set slots to an event. Use this when an event was created without a line-up, or to extend one — `create_event` does not generate slots by itself. The batch is all or nothing: every slot is validated before anything is written, so a single bad `event_floor_id` rejects the whole call and leaves the event untouched. Slots come back in the same shape `GET` returns, so the response can be used directly to pick a `slot_id` for `POST /api/v1/slots/{slotId}/book`. An event belonging to a different organisation returns 404, not 403.
Auth: apiKeyAuth
Parameters
-
eventId· path · required
Request body (required)
-
slots· array · required 1–50 slots to create
{
"slots": [
{
"slot_label": "Opening",
"start_time": "23:00",
"end_time": "01:00",
"set_length_minutes": 120,
"genres": [
"Techno"
]
},
{
"slot_label": "Headline",
"start_time": "01:00",
"end_time": "03:00",
"set_length_minutes": 120,
"is_open_call": true
}
]
} Responses
-
201The created slots -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
404The resource does not exist, or belongs to another organisation. These are deliberately indistinguishable — the API never confirms that another promoter's records exist. -
409The event is archived -
422slots empty / longer than 50, or an event_floor_id from another event -
429Too many requests. Retry after the number of seconds in `Retry-After`.
POST /api/v1/slots/{slotId}/book Offer a slot to a DJ who is on djstrich
Creates a booking REQUEST for the given slot and notifies the DJ. It does not assign them: the slot stays open until the DJ accepts, and only their acceptance fills it. This is the same contract the web app has, and it is deliberate — a promoter cannot put a DJ's name on a public line-up without their consent. The event and organisation are derived server-side from `slotId`; any values you send for them are ignored. A slot belonging to another organisation returns 404. For a DJ who does not have a profile yet, use `POST /api/v1/djs/invite` instead. Rate limit: booking creation is additionally capped per user per 24 hours (the same cap the web app applies). Exceeding it returns 429 with `retryAfterSeconds`.
Auth: apiKeyAuth
Parameters
-
slotId· path · required
Request body (required)
-
dj_profile_id· string · required From GET /api/v1/djs -
message· string · required Message to the DJ, 1–500 characters -
set_time· string | null HH:MM -
set_length_minutes· integer | null -
budget_min· number | null -
budget_max· number | null
{
"dj_profile_id": "8f14e45f-ceea-467a-9c8b-1f5c9c9c1a11",
"message": "Freitag, 2 Uhr Slot im Hauptraum. 90 Minuten, Techno.",
"set_time": "02:00",
"set_length_minutes": 90
} Responses
-
201Booking request created; the DJ has been notified -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
404The resource does not exist, or belongs to another organisation. These are deliberately indistinguishable — the API never confirms that another promoter's records exist. -
409The slot already holds a confirmed booking -
422Validation error (missing dj_profile_id or message, message length) -
429Too many requests. Retry after the number of seconds in `Retry-After`.
GET /api/v1/webhooks List your webhook endpoints
Read-only. Registering and deleting endpoints is dashboard-only, on purpose: every API key is minted by an org owner, so a leaked key could otherwise register an attacker-controlled endpoint that keeps receiving your bookings even after the key is revoked. Keeping writes behind a browser session means revoking a key actually stops the leak.
Auth: apiKeyAuth
Responses
-
200Registered endpoints and the available event types -
401Missing, invalid, revoked or expired API key. The body is the v1 error envelope, whose `code` is one of missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. -
403The key was not issued by an organisation owner