Sari la conținutul principal
For AI agents, tool builders and developers

One POST, and the show is on sale.

27 REST endpoints over the whole product: three to get a key, then twenty-four behind it covering schedules, sub-schedules, events, recurrences, ticket types, sales and refunds, feedback and fan content. JSON in, JSON out, one header.

An OpenAPI 3.0 spec, llms.txt and agents.json ship with it, so an agent can discover this API and drive it without a human reading the docs first.

POST /api/events/synth-lab X-API-Key
{
  "name": "Analog Night",
  "starts_at": "2026-08-14 20:00:00",
  "duration": 3,
  "tickets_enabled": true,
  "tickets": [{
    "type": "Advance",
    "price": 18,
    "quantity": 120
  }]
}
201 CREATED application/json
{ "data": {
  "id": "Kd3Vq7",
  "url": "https://synth-lab.eventschedule.com/analog-night/Kd3Vq7",
  "tickets": [{ "id": "9pR3vB", "type": "Advance" }]
}, "meta": { "message": "Event created successfully" } }

That one call also writes the event to Google, Outlook or CalDAV if the schedule is connected to one, and fires an event.created webhook.

02 · the contract

Four facts, and you can start writing.

No SDK to install, no OAuth dance, no sandbox to request. The whole surface behaves the same way, which is the only property an agent really needs.

X-API-Key

One header

Register, generate a key in your settings, or log in when you have none. Keys last a year. Nothing else is required.

300 / 30

Requests a minute

300 reads and 30 writes a minute, per IP. Over the line you get a 429, not a silent drop.

per_page ≤ 500

The big lists paginate

100 by default, 500 at most, with a meta block carrying the page, the total and the bounds. Categories and sub-schedules come back whole.

Kd3Vq7

IDs are opaque strings

Never a sequential integer, and an event's is the same string that appears in its public URL, so you can build a link from a response.

2xx the success envelope
{
  "data": [ ... ],
  "meta": { "current_page": 1, "total": 50 }
}
422 field-level errors, always in the same place
{
  "error": "Validation failed",
  "errors": { "starts_at": ["must match Y-m-d H:i:s"] }
}

401 for a bad key, 403 when the plan or the permission is missing, 404, 409 when a key is already live or a refund's outcome is not yet confirmed, 422 with the offending fields named, 429 when throttled. A model can branch on that without guessing.

03 · the ledger

The entire surface, on one page.

27 endpoints. Not a summary of them, all of them. Everything past /api/login needs the key header, and API access is part of the Pro plan.

Every Event Schedule API endpoint, grouped by resource, with its HTTP method, path and behaviour
Method Path Behaviour
Auth No key required. Each of these has its own throttle.
POST /api/register/send-code Mail a six-digit verification code. Hosted mode only, five per address per hour.
POST /api/register Create the account and return a key. Three per IP per hour.
POST /api/login Mint a key for an account that has none. A live key returns 409 instead.
Schedules A schedule is the tenant: a venue, a talent or a curator.
GET /api/schedules List the schedules you own or administer. Filter by name and by type.
GET /api/schedules/{subdomain} One schedule, with its sub-schedules inlined.
POST /api/schedules Create one. name and type are required; the subdomain is generated from the name.
PUT /api/schedules/{subdomain} Name, contact, description, timezone, language, address. Partial payloads are fine.
DELETE /api/schedules/{subdomain} Marks it deleted, so it and its pages go dark. Owner level only, not admin.
Sub-schedules Named strands inside one schedule, each with its own colour.
GET /api/schedules/{subdomain}/groups id, name, slug and colour for each sub-schedule.
POST /api/schedules/{subdomain}/groups name is required, colour optional. The slug is generated.
PUT /api/schedules/{subdomain}/groups/{group_id} Rename it or recolour it.
DELETE /api/schedules/{subdomain}/groups/{group_id} Events survive; their sub-schedule reference is cleared.
Events The big one. Tickets, agenda parts, members and recurrence all ride along.
GET /api/events Paginated, newest first. Ten filters, including tickets_enabled and rsvp_enabled.
GET /api/events/{id} One event with its tickets, members and agenda parts.
POST /api/events/{subdomain} Create an event on a schedule. Carries its own 30-per-minute throttle.
PUT /api/events/{id} Partial update. Recurrence, tickets and agenda parts survive being omitted.
DELETE /api/events/{id} Delete it, and withdraw it from any synced calendar.
POST /api/events/flyer/{event_id} Multipart upload of a flyer_image for an existing event.
Categories Read-only lookups so you can send a category_id you know exists.
GET /api/categories Every system category, with its id and name.
GET /api/categories/{subdomain} The effective list for one schedule, including its own custom categories.
Sales A sale is a buyer, a set of ticket quantities and a status.
GET /api/sales Filter by event, subdomain, status, buyer email or occurrence date.
GET /api/sales/{id} One sale with its ticket lines.
POST /api/sales Book a sale by hand. Created unpaid; free tickets are marked paid straight away.
PUT /api/sales/{id} mark_paid, cancel or refund. A refund sends Stripe or PayPal money back, in full or an amount you name; an idempotency_key makes a retry safe.
DELETE /api/sales/{id} Soft delete. It stops appearing in listings.
Feeds Read-only, for pulling audience content onto a site you already run.
GET /api/feedback Post-event star ratings and comments. Filter by minimum rating and date range.
GET /api/fan-content Approved comments, photos and videos. Submitter email addresses are never included.
Colour is the verb GETread POSTcreate PUTupdate DELETEremove

Full documentation for each one, with a cURL example and a response body, is in the API reference.

05 · exchanges

Three calls you will actually write.

Filtering a calendar, standing up a weekly residency, and settling a sale. Everything else is a variation on these.

GET /api/events
?subdomain=synth-lab
&starts_after=2026-08-01
&tickets_enabled=1
&per_page=50
200 OK
"meta": {
  "current_page": 1,
  "last_page": 2,
  "total": 63
}

Ten filters on the events list, including whether tickets or RSVP are switched on, a venue, a sub-schedule and a date window. You narrow server-side rather than pulling a year and filtering in the agent.

POST /api/events/synth-lab
"schedule_type": "recurring",
"recurring_frequency": "weekly",
"days_of_week": "0111110",
"recurring_end_type": "after_events",
"recurring_end_value": "14"
201 CREATED one event, fourteen dates
"schedule_type": "recurring",
"days_of_week": "0111110"

A seven-character mask, Sunday first, so Monday to Friday is "0111110". Frequency is one of daily, weekly, every_n_weeks, monthly_date, monthly_weekday or yearly, and a run can end never, on a date, or after a set number of occurrences.

PUT /api/sales/7bQx2m
{ "action": "mark_paid" }
200 OK
"status": "paid",
"payment_amount": 36,
"total_quantity": 2,
"tickets": [{ "type": "Advance" }]

Three actions, and which ones are legal depends on where the sale is: mark_paid from unpaid, refund from paid, cancel from either. On a Stripe or PayPal sale, refund sends the money back before the status moves, and an amount makes it partial, which leaves the sale paid. You can also create a sale outright for a buyer who paid you off-platform.

how matching works

You can name a venue or a performer instead of looking up an ID: send venue_name with venue_address1, or members as a list of names and emails, and the API resolves them to schedules on your account. To be exact about what that is: it matches an existing schedule you own or follow, and returns a 422 naming the one it could not find. It does not invent a venue for you. Categories work the same way: send category as a name and it is matched against that schedule's category list.

06 · webhooks

Or stop asking, and get told.

Polling a sales endpoint every minute is a waste of both our time. Register an endpoint and the traffic reverses: we POST to you, signed, the moment something happens.

POST https://your-app.example/hooks
X-Webhook-Event: sale.paid
X-Webhook-Signature: sha256=<hex>
X-Webhook-Timestamp: 2026-08-14T20:11:04+00:00
User-Agent: EventSchedule-Webhook/1.0

{ "event": "sale.paid", "data": { ... } }

The signature is an HMAC-SHA256 of the raw body, keyed on a secret shown once when you add the hook. Verify it before you trust the payload. Key on data.id plus the event type, because a delivery can repeat and one sale fires several types. There is a delivery log in your settings when something goes wrong.

Webhook reference, with verification snippets

Fourteen event types pro
  • sale.created A sale is created, still unpaid.
  • sale.paid Confirmed paid, whether by Stripe, PayPal, Payfast, Invoice Ninja, by hand or free.
  • sale.refunded A paid sale is refunded in full. A partial refund leaves it paid and fires nothing.
  • sale.cancelled A sale is cancelled.
  • installment.paid A payment of an installment plan is collected.
  • installment.failed A scheduled payment could not be collected. Read outcome for why.
  • event.created A new event exists.
  • event.updated An event changed.
  • event.deleted An event is gone.
  • event.cancelled An event is cancelled.
  • ticket.scanned A ticket QR code is scanned at the door.
  • ticket.booked A pass holder reserves a place on a date.
  • ticket.booking_cancelled A pass holder releases a reserved place.
  • feedback.submitted An attendee left a rating.
07 · the rest

The parts that make it safe to automate.

Details that only matter once your code is running unattended, which is exactly when they matter most.

free and pro open source

Your own install, same API

Event Schedule is open source, and the API does not change when you host it yourself: same routes, same OpenAPI spec, same discovery files, served from your own domain. On a selfhosted install the Pro gate returns true unconditionally, so no endpoint is held back and no key talks to anyone else's server.

docker or bare metal your database no outbound calls required

How selfhosting works

pro

Flyers, as a second call

Artwork is multipart, so it gets its own request. Create the event, then POST a flyer_image to the flyer endpoint with the returned ID.

curl -X POST .../api/events/flyer/Kd3Vq7 \
  -H "X-API-Key: $KEY" \
  -F "[email protected]"
free and pro

Languages, made explicit

Set language_code on a schedule and its pages are served in that language; twelve are supported. A schedule can also nominate one translation target, and its own copy is machine-translated into it on a scheduled pass.

ar de en es et fr he it nl pt ro ru
pro

Money, without a middleman

Ticket types created through the API sell through your own Stripe or PayPal account, or through Invoice Ninja, Payfast for rand prices, a payment URL, or by hand. Event Schedule takes zero platform fees on ticket sales: the only deduction is your processor's. Sales come back through the sales endpoints and through sale.paid webhooks, with the ticket lines attached, and a Stripe or PayPal refund goes back through the provider.

stripe paypal payfast invoiceninja payment_url cash
pro

Read-only feeds

Two endpoints exist purely so you can pull audience content somewhere else: post-event ratings and comments, and approved fan photos, videos and comments. Fan submissions carry a display name only; the ratings feed names the attendee, so treat it as owner-facing.

Each kind of fan submission has its own ID sequence, so key on type and id together when you store a row.

pro

Partial writes that keep their nerve

PUT takes the same body as create and applies only what you send. Recurrence configuration, ticket types and agenda parts are preserved when they are absent, so an agent that only knows the new start time cannot quietly erase a run's ticket tiers. Every write is scoped to the schedules the key's owner owns or administers, and anything outside that returns 403 rather than silently doing nothing.

401

Key missing, wrong or expired.

403

Not your schedule, or the plan does not cover it.

429

Throttled. Back off and retry.

08 · callers

What people point at this API.

An HTTP API has no opinion about what is calling it, which is the point.

AI Assistants

Turn a conversation into a published event. Register, create the schedule and create the event in three calls, then hand back the URL from the response.

Learn more

Developer Tools & Scripts

A cron job, a CLI, a one-off migration. Generate a client from the OpenAPI spec and the whole surface is typed for you.

Learn more

Community Bots

A Discord, Slack or Telegram bot that creates the event when someone announces it in the channel, and posts the ticket link back.

Learn more

Booking Platforms

Keep your own front end and let Event Schedule hold the events, the ticket types and the sales. Webhooks push each paid sale straight back to you.

Learn more

Calendar Aggregators

Pull a date window with the events filters, or take the iCal feed and skip the API entirely. Both come off the same schedule.

Custom Integrations

Anything that speaks HTTP and JSON. If you would rather not write the client, the OpenAPI spec will write it for you.

Learn more
09 · first run

Three requests from nothing to a live page.

01

Get a key

One unauthenticated POST to /api/register, or to /api/register/send-code first in hosted mode. The response body carries the key and its expiry.

X-API-Key: es_live_...
02

Create a schedule

POST /api/schedules with a name and a type. The subdomain is generated from the name and the public page exists immediately.

{"name": "Synth Lab", "type": "venue"}
03

Create events

POST /api/events/{subdomain}. Ticket types, agenda parts and recurrence go in the same body, so there is no second round trip. On eventschedule.com this one needs the schedule on Pro.

{"name": "Analog Night", "duration": 3}

Free forever. Upgrade when you're ready.

Free registration for as many people as you like. When you start charging, zero platform fees on every plan - the only deduction is your payment processor's own.

Free
$0 forever, no card

Everything you need to publish a schedule and fill the room, for as long as you like.

  • Unlimited events, sub-schedules and recurring dates
  • Two-way Google, Outlook and CalDAV sync
  • Unlimited free registration and RSVPs, scanned at the door

No expiry, and no card asked for.

Pro Most picked
$5 per month

Start charging, and the rest of the selling kit arrives with it.

  • Sell paid tickets, with the live check-in dashboard
  • Promo codes, add-ons, passes and gift cards
  • The REST API, webhooks, and no Event Schedule branding

7-day free trial, cancel any time.

Enterprise
$15 per month

For a room with a seating chart, a team, and a domain of its own.

  • Reserved seating, drawn once and reused every date
  • Your own domain, and up to five team members
  • Internal and unlisted events, and 1,000 newsletter recipients

Included free on every selfhosted install.

10 · questions

Frequently asked questions

What developers ask before they write the first request.

Is the API free to use?

The REST API is part of the Pro plan at $5 a month, with a seven-day trial when you subscribe. Selfhosted installations are Pro by definition, so running your own copy unlocks every endpoint at no cost. Ticket sales carry zero platform fees on every plan and in both modes: you keep everything except your payment processor's cut.

Which endpoints need the Pro plan, and what happens without it?

On eventschedule.com the list endpoints for schedules, events, sales and feedback return only rows from schedules on Pro, so a free schedule's data is missing rather than refused: check the plan before you read an empty list as nothing there. Reading one schedule, event or sale, updating a schedule, and writing to its events, sub-schedules or sales return 403 with "API usage is limited to Pro accounts". The exception worth planning around is POST /api/sales, which has no plan check of its own: a free schedule can record a sale against a zero-price ticket, while any row with a price on it needs the schedule to be Pro and answers 422 otherwise. On a selfhosted install every one of these checks passes.

How does authentication work?

One header, X-API-Key. Get a key from POST /api/register or POST /api/login, or generate one in your account settings. Keys are valid for a year. Login only mints a key when the account has none, and returns 409 while one is still live, so store the key rather than calling login on every run. Accounts with two-factor authentication have to generate keys from the web UI. Every endpoint except register, send-code and login requires the header.

What can I actually do with it?

27 endpoints across registration, schedules, sub-schedules, events, categories and sales, plus two read-only feeds for post-event feedback and fan-submitted content. Schedules, sub-schedules, events and sales have full create, read, update and delete; categories are read-only lookups. A single create call can carry ticket types, agenda parts, performing members, a venue and a recurrence pattern, so publishing a run of shows is one request rather than six.

Can the API refund a sale?

Yes: PUT /api/sales/{id} with the action refund. On a Stripe or PayPal sale the money goes back through the provider before the status changes. Send an amount to return part of it, and the sale stays paid with its tickets valid; leave it out and the whole remaining balance goes back, the sale becomes refunded, its places return to stock and sale.refunded fires. Send an idempotency_key of your own so a retry returns the first attempt's outcome instead of refunding twice. A refund whose outcome could not be confirmed returns 409 and is never retried for you. Invoice Ninja, Payfast, payment-link and cash sales are only recorded as refunded, so return that money yourself, and a payment plan is refunded in full only.

What is llms.txt, and why are there two of them?

llms.txt is an emerging convention for telling a language model what a site is and where its documentation lives. Event Schedule publishes both: llms.txt is a short routing summary an agent can read to decide whether this API is relevant, and llms-full.txt is the entire reference in one file, so an agent that has decided to proceed never needs to follow a link.

What are the rate limits?

300 GET requests a minute and 30 POST, PUT or DELETE requests a minute, counted per IP address. Creating an event carries its own 30-per-minute throttle on top of that, and the auth endpoints have their own tighter limits. Going over returns 429 with an error body.

Are there webhooks, or do I have to poll?

There are webhooks, on the Pro plan. Fourteen event types cover sales, event changes, door scans, pass bookings and feedback. Each delivery is signed with HMAC-SHA256 in an X-Webhook-Signature header so you can verify it came from us, payloads match the shapes the API returns, and there is a delivery log in your settings for debugging.

Which IDs does the API use?

Encoded strings, never sequential integers. An event, a ticket, a sale and a sub-schedule all identify themselves with a short opaque string, and an event's is the same string that appears in its public URL, so you can build a link straight from a response. Category IDs are the exception: they are small integers you read from the categories endpoint.

Can I run this against my own installation?

Yes. Event Schedule is open source and the API is the same in both modes: same routes, same spec, same discovery files served from your own domain. On a selfhosted install the Pro gate returns true unconditionally, so nothing is held back.

11 · your key

The last call is the first one.

Pick a name and start, or register straight from your code. Publishing a schedule and its dates is free forever, and so is free registration; the API and any ticket with a price on it are $5 a month, and Event Schedule takes nothing from the door.

.eventschedule.com
Get started free

No card to start. Or go straight to the API reference and the OpenAPI spec.

POST /api/register no key required
{
  "name": "Your Agent",
  "email": "[email protected]",
  "password": "..."
}
201 CREATED
{ "data": {
  "api_key": "your_new_api_key",
  "api_key_expires_at": "2027-07-30T00:00:00Z"
} }

In hosted mode, POST /api/register/send-code mails a six-digit code first, and you pass it as verification_code.