Create Event
POST /events
You can create an event by providing the following fields:
| Property | Type | Description |
|---|---|---|
name* | string | Name of the event. Not sent for a date in a series, which has the name of its main event |
description | string | Description of the event, as rich text (see Rich text below). May be null |
masterLanguage | string | The language name and description are written in, as a lowercase ISO 639-1 code. Defaults to your museum's language |
translations | array of EventTranslation | The event in other languages, each as { "language": "da", "name": "…", "description": "…" }, with description as rich text. name and description may be null. Replaces all of the event's translations |
startDate* | string | When the event starts, in the museum's local time (see Times below) |
endDate | string | When the event ends. May be null. Not before startDate |
allDay | boolean | Whether the event lasts all day: only the days of startDate and endDate count. Defaults to false |
published | boolean | Whether the event is publicly visible. Defaults to false |
publishedAt | string | When the event is published. Once set, it decides instead of published: the event is published from this time on. May be null |
saleStartsAt | string | When the sale starts. null, the default, starts the sale when the event is published |
saleEndsAt | string | When the sale ends. null, the default, ends the sale when the event ends |
imageId | string (UUIDv4) | Id of an image uploaded with POST /images. May be null |
enableTickets | boolean | Whether tickets can be issued for the event. Defaults to true |
maxTickets | number | The most tickets for the event, shared by all its ticket types: a whole number of 1 or more. null, the default, is no overall limit |
parentEventId | string (UUIDv4) | Adds the event as a new date to the series of this event (see Series below). Can only be set when the event is created |
The event gets the mode (live or test) of your API key. Like an event created in the back office, a new event is unpublished, has tickets on, and is on sale from when it is published until it ends. It is available online and in your app (the point of sale can be switched on in the back office), shows its image in the checkout, and gets your museum's default event settings (checkout fields, waiting list and booking fee).
The response is the Event, the same object Get Event returns. A new event has no ticket types yet: add them with Create Event Ticket Type. It shows up in List Events a moment after it is created.
{ "name": "Summer Concert", "description": "[{\"type\":\"paragraph\",\"children\":[{\"text\":\"Chamber music in the garden\"}]}]", "translations": [ { "language": "da", "name": "Sommerkoncert", "description": "[{\"type\":\"paragraph\",\"children\":[{\"text\":\"Kammermusik i haven\"}]}]" } ], "startDate": "2027-06-12T19:00:00.000Z", "endDate": "2027-06-12T21:00:00.000Z", "imageId": "6ef7d631-b898-40a4-ba27-b8c772f6db03", "maxTickets": 120 }
Response:
{ "id": "5c1f8e2a-3b4d-4e6f-9a7b-8c9d0e1f2a3b", "createdAt": "2026-11-03T09:00:00.000Z", "masterLanguage": "en", "name": "Summer Concert", "description": "[{\"type\":\"paragraph\",\"children\":[{\"text\":\"Chamber music in the garden\"}]}]", "startDate": "2027-06-12T19:00:00.000Z", "endDate": "2027-06-12T21:00:00.000Z", "allDay": false, "published": false, "onSale": false, "saleEndsAt": "2027-06-12T21:00:00.000Z", "imageId": "6ef7d631-b898-40a4-ba27-b8c772f6db03", "enableTickets": true, "soldOut": false, "translations": [ { "language": "da", "name": "Sommerkoncert", "description": "[{\"type\":\"paragraph\",\"children\":[{\"text\":\"Kammermusik i haven\"}]}]" } ], "checkoutUrl": "https://acme-museum.membership.loby.io/events/5c1f8e2a-3b4d-4e6f-9a7b-8c9d0e1f2a3b/checkout", "maxTickets": 120, "numberOfIssuedTickets": 0, "totalNumberOfReservedTickets": 0, "remainingTickets": 120, "ticketTypes": [] }
In the response, saleStartsAt and saleEndsAt are when the sale really starts and ends. Here the sale starts when the event is published, which has no time yet, so saleStartsAt is left out, and it ends with the event.
Times
Event times (startDate, endDate, publishedAt, saleStartsAt and saleEndsAt) are the museum's local time, written in ISO 8601 ending in Z, exactly as Get Event returns them: 2026-07-01T14:00:00.000Z is 14:00 at the museum, whatever its time zone. Seconds and milliseconds may be left out (2026-07-01T14:00Z), but a time with an offset such as +02:00 is refused. Only createdAt is real UTC.
For an all-day event only the day counts: it lasts from the start of the day of startDate to the end of the day of endDate (or of startDate, without an endDate).
Rich text
An event's description, and the description of each translation, is rich text: a string holding a JSON array of blocks, exactly as Get Event returns it. Each paragraph is a block with a type and its children. A child is a text leaf, { "text": "…" } with bold, italic or underline set to true when needed, or a block of its own.
Use these block types: paragraph, heading-one, bulleted-list (whose children are list-item blocks), and link, which has a url and sits among the text of a block. Descriptions written in the back office can hold other types too, such as heading-two, numbered-list or quote, but the online checkout doesn't show those yet.
A description that isn't such a string, plain text included, gives EVENT_INVALID_DESCRIPTION. null removes it.
Two paragraphs, the second with a bold part and a link, followed by a list:
[ { "type": "paragraph", "children": [{ "text": "Doors open at 18:30." }] }, { "type": "paragraph", "children": [ { "text": "Bring a blanket", "bold": true }, { "text": " and see the " }, { "type": "link", "url": "https://example.com/garden", "children": [{ "text": "garden map" }] }, { "text": "." } ] }, { "type": "bulleted-list", "children": [ { "type": "list-item", "children": [{ "text": "Picnic baskets for sale" }] }, { "type": "list-item", "children": [{ "text": "Free coffee" }] } ] } ]
Send it as a string, for example made with JSON.stringify. The first paragraph alone is "[{\"type\":\"paragraph\",\"children\":[{\"text\":\"Doors open at 18:30.\"}]}]".
Series
A series is a main event with dates. Each date is an event of its own, with its own times, publishing, sale, tickets and capacity, and its parentEventId is the id of the main event.
To add a date, create an event with parentEventId set to the main event. The id of one of its dates works too: the new date joins the same series. A date takes everything else from its main event, such as its name, description, language and translations, image, allDay, enableTickets, maxTickets and mode. So a date only takes startDate, endDate, published, publishedAt, saleStartsAt and saleEndsAt, and any other field gives SET_ON_MAIN_EVENT.
A new date is unpublished and not on sale until you set them: send published (or publishedAt), and both saleStartsAt and saleEndsAt (null for each puts it on sale from when it is published until the date ends):
{ "parentEventId": "5c1f8e2a-3b4d-4e6f-9a7b-8c9d0e1f2a3b", "startDate": "2027-06-19T19:00:00.000Z", "endDate": "2027-06-19T21:00:00.000Z", "published": true, "saleStartsAt": null, "saleEndsAt": null }
A date's ticket types are copies of its main event's, each with its own id and its own count of tickets. Ticket types are created and changed on the main event, and the changes are copied to its dates. The response can list a new date's ticket types a moment late: Get Event shows them.
Errors
Errors are returned as { "errors": [{ "message": "EVENT_NAME_REQUIRED" }] }.
| Status | Message | When |
|---|---|---|
400 | EVENT_NAME_REQUIRED | name is missing or blank, and the event isn't a date in a series |
400 | EVENT_END_BEFORE_START | endDate is before startDate |
400 | EVENT_INVALID_MAX_TICKETS | maxTickets is below 1 |
400 | EVENT_INVALID_DESCRIPTION | description, or the description of a translation, isn't rich text as under Rich text |
400 | PARENT_EVENT_NOT_FOUND | parentEventId isn't an event of your museum, was deleted, or has the other mode (live or test) than your API key |
400 | SET_ON_MAIN_EVENT | The event is a date (it has parentEventId) and the body holds a field other than startDate, endDate, published, publishedAt, saleStartsAt and saleEndsAt |
400 | LANGUAGE_NOT_SUPPORTED | masterLanguage or the language of a translation isn't a language Loby supports |
400 | FIELD_NOT_ALLOWED: <field> | The body holds a field that isn't listed above. For a field in a translation, <field> is translations.<field> |
400 | INVALID_BODY | The body isn't a JSON object, startDate is missing, a translation has no language, a field has the wrong JSON type, a field that may not be null is null, or a time isn't written as under Times |
403 | NO_ACCESS | Your API key may not create events |