For a developer or technical helper of a customer. With the API, your website, CRM or accountant can read data from Ultimate Bookings. Questions? info@ultimate-bookings.com.
The names in the API itself (sources, fields, parameters and error codes) are Dutch, for example artiest (artist) or limiet (limit). Use them exactly as written; this guide explains each one in English. The screens in Ultimate Bookings are still in Dutch, so we name the Dutch labels you will see there.
What it is
An API you can only read from. Writing, changing or deleting is not possible. Every key belongs to one organisation and sees only that organisation's data. Where that boundary lies is not an agreement in our code that someone could forget: every request goes through the database, which checks the key and returns only rows of that organisation.
There are two kinds of key:
Full key, starts with ub_live_. For a connection with a CRM, an accountant or your own software. Never put it in a website. It needs the API in your plan: that is included in Ultimate Artist and Ultimate Agency. With Artist and Agency you can add it for €15.00 per month excl. VAT.
Website key, starts with ub_web_. Free in every plan. It only returns your public shows (date, venue, city, country, artist) and can therefore safely sit in the code of your website.
Creating a key
In Ultimate Bookings, go to Instellingen, API-sleutels (Settings, API keys). Only the owner of the organisation can create, pause and delete keys here.
Give the key a name (up to 80 characters), for example Website or CRM.
Choose the kind: website key or full key.
For a full key, tick what it may read: Shows (always on), Agenda, Contacten (contacts), Artiesten (artists) and Geld (money). Money is off by default: only once you tick it does the key see fees and invoices.
Choose Sleutel maken (create key). The key is then shown once. Copy it straight away. We only store a derived value (a hash), so we cannot show it to you again. Lost it? Delete it and create a new one.
You can pause a key: it stops working immediately and you can switch it back on later. Deleting is permanent. The list shows for each key when it was last used and how often.
A show only appears for the website key once you have ticked Op je website tonen (show on your website) yourself in the booking form. It is off by default, so a private party or an option (hold) never becomes public by itself.
Authenticating with your key
Send the key in the header of every request:
Authorization (HTTP header)
Authorization: Bearer ub_live_YOUR_KEY
A website key may also go in the address, as ?sleutel=ub_web_... (sleutel means key). That is meant for a browser: this source can be reached from any website (CORS is open), so you can fetch directly from your own page. This only works for openbare-shows. A full key in the address is ignored; it must always go in the header and belongs on a server only.
The six sources
All addresses are GET and start with https://ultimate-bookings.com/api/v1. A full key needs the permission listed; every full key has Shows. Fields without a value are left out of the response instead of being null.
The sources of the API with the permission they need and their parameters
limiet (1 to 200, default 100), artiest (id of an artist, optional)
Upcoming public shows, for your website
Parameters
van (from) and tot (to): a date like 2026-10-01, in Amsterdam time. Both days count. For shows and agenda this is the start time, for invoices the invoice date.
limiet (limit): how many rows at a time, a whole number from 1 to 500. Default 100.
vanaf (offset): how many rows to skip, for the next page. Default 0.
A parameter that a source does not use is still checked for validity, but otherwise does nothing.
GET /api/v1/shows
Permission: Shows (always on). Parameters: van, tot, limiet, vanaf.
All shows of your organisation, sorted by start time, with every status (from enquiry to paid, and cancelled). Filter on the status field yourself if you only want confirmed shows. van and tot refer to the start time of the show.
Fields
id
titel
title
status
inquiry, quote, option, confirmed, performed, invoiced, paid, cancelled or no_show
soort
type
start
einde
end
hele_dag
all day
zaal
venue
stad
city
land
country code
artiest_id
artist id
artiest
name of the artist
openbaar
is Op je website tonen (show on your website) ticked for this show?
Only with the Money permission
gage
fee, in the currency of the munt field, with two decimals
munt
currency
GET /api/v1/agenda
Permission: Agenda. Parameters: van, tot, limiet, vanaf.
The calendar items that are not a show: blocks, travel and notes, sorted by start time. van and tot refer to the start time.
Fields
id
soort
blokkade (block), reis (travel) or notitie (note)
reis_soort
type of travel
titel
title
start
einde
end
hele_dag
all day
plaats
place
artiest_id
artist id
show_id
the show this belongs to, if there is one
GET /api/v1/contacten
Permission: Contacts. Parameters: limiet, vanaf.
Your address book, sorted by name. Anonymised contacts are left out. van and tot do nothing here.
Fields
id
naam
name
bedrijf
company
soort
type
email
email_2
second email address
telefoon
phone
straat
street
postcode
postcode
stad
city
land
country code
btw_nummer
VAT number
Notes, travel profile, allergies and the unsubscribe key are deliberately left out.
GET /api/v1/artiesten
Permission: Artists. Parameters: none (all artists at once).
All your artists, sorted by name, in one go. There is no pagination here: limiet, vanaf, van and tot do nothing.
Fields
id
naam
name
soort
type
kleur
colour
actief
active
thuisstad
home city
thuisland
home country
IBAN, Dutch Chamber of Commerce (KvK) number and VAT number are deliberately left out.
GET /api/v1/facturen
Permission: Money. Parameters: van, tot, limiet, vanaf.
Your invoices, sorted by invoice date, without drafts. van and tot refer to the invoice date here (both days count). Amounts are in whole euros with two decimals, not in cents.
Fields
id
nummer
invoice number
status
soort
type
factuurdatum
invoice date
vervaldatum
due date
betaald_op
paid on
excl_btw
amount excluding VAT
btw
VAT
totaal
total
munt
currency
btw_verlegd
VAT reverse-charged
artiest_id
artist id
show_id
klant
company name of the contact
contact
name of the contact
GET /api/v1/openbare-shows
Permission: Website key. Parameters: limiet (1 to 200, default 100), artiest (id of an artist, optional).
Only shows where you have ticked "Op je website tonen" (show on your website), that are confirmed (or already performed, invoiced or paid) and that take place today or later, by date. This is the only source for a website key, and the only one a website key may read. If each artist has their own site, pass artiest with the artiest_id from the response: you then get only that artist's shows. van, tot and vanaf do nothing here.
Fields
datum
date, YYYY-MM-DD, in Amsterdam time
start
missing for a show that lasts all day
zaal
venue
stad
city
land
country code
artiest
name of the artist
artiest_id
for the artiest filter
Responses, errors and limits
A successful request returns a data list and a meta block. aantal (count) is the number of rows in this response, limiet and vanaf are the values we used.
On an error you get an error block with a fixed code (which you can check against) and a message (readable, in Dutch, and it may change):
Example of an error (JSON)
{
"error": {
"code": "geen_recht",
"message": "Deze sleutel mag dit onderdeel niet lezen. Zet het vinkje aan bij Instellingen, API-sleutels."
}
}
Error codes with HTTP status and meaning
Status
Code
Meaning
400
ongeldige_datum
van or tot is not a date like 2026-10-01 (year 1900 to 2100), or van is after tot.
400
ongeldige_limiet
limiet is not a whole number from 1 to 500 (200 for openbare-shows).
400
ongeldige_artiest
artiest is not the id of an artist.
400
ongeldige_vanaf
vanaf is not a whole number of 0 or more.
401
sleutel_ontbreekt
No key was sent.
401
sleutel_ongeldig
The key does not exist, is paused or has been deleted.
402
geen_api_in_pakket
A full key only works with the API in your plan (see above).
403
geen_recht
The key does not have this permission, or is the wrong type for this source.
403
geen_pakket
The organisation has no active plan or has been closed.
404
onbekende_bron
This address after /api/v1/ does not exist.
429
te_veel_verzoeken
You are over the limit. The Retry-After header says how many seconds to wait.
500
storing
Something went wrong on our side. Try again shortly.
Limits
At most 60 requests per full key per minute. A website key may make 3000 per minute, because all your visitors share it.
At most 120 requests per IP address per minute, across all keys together.
Go over that and you get status 429 with the Retry-After header (in seconds). Wait that long and try again.
Responses from openbare-shows may be cached for a minute (with ?sleutel= in the address also by our network), so a new show, or a website key you pause, can take a minute to come through. All other sources are never cached and a paused full key stops immediately.
With a website key. This fills a list on your page and inserts the data as text, not as HTML, so odd characters in a venue name cannot break anything. Want to try it first:
<ul id="ub-shows"></ul>
<script>
var KEY = "ub_web_YOUR_KEY";
var ENDPOINT = "https://ultimate-bookings.com/api/v1/openbare-shows?limiet=10&sleutel=" + KEY;
var list = document.getElementById("ub-shows");
fetch(ENDPOINT)
.then(function (response) {
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
})
.then(function (result) {
if (result.data.length === 0) {
list.textContent = "No shows announced yet.";
return;
}
result.data.forEach(function (show) {
// datum is a plain day (YYYY-MM-DD); read it in UTC so it never shifts by a day.
var date = new Date(show.datum + "T12:00:00Z").toLocaleDateString("en-GB", {
day: "numeric", month: "long", year: "numeric", timeZone: "UTC"
});
var place = [show.zaal, show.stad].filter(Boolean).join(", ");
var item = document.createElement("li");
item.textContent = date + ": " + place + (show.artiest ? " (" + show.artiest + ")" : "");
list.appendChild(item);
});
})
.catch(function () {
list.textContent = "The list of shows could not be loaded.";
});
</script>
Fetching everything with pagination
Request one page at a time and increase vanaf by limiet, until a response has fewer rows than the limit. The order is fixed (by start time and then id), so a page never comes back twice or gets skipped as long as nothing changes while you fetch. This works for shows, agenda, contacten and facturen (invoices).
Pagination with waiting on 429 (JavaScript)
// Node 18 or newer. Fetches all shows, 500 at a time.
const KEY = process.env.UB_KEY; // ub_live_...
const LIMIT = 500;
async function fetchAllShows() {
const all = [];
let offset = 0;
while (true) {
const url = "https://ultimate-bookings.com/api/v1/shows?limiet=" + LIMIT + "&vanaf=" + offset;
const response = await fetch(url, { headers: { Authorization: "Bearer " + KEY } });
if (response.status === 429) {
const wait = Number(response.headers.get("Retry-After")) || 60;
await new Promise((done) => setTimeout(done, wait * 1000));
continue; // the same page once more
}
if (!response.ok) {
const { error } = await response.json();
throw new Error(error.code + ": " + error.message);
}
const { data, meta } = await response.json();
all.push(...data);
if (meta.aantal < LIMIT) return all; // last page
offset += LIMIT;
}
}
What it can't do (yet)
Nothing can be written, changed or deleted: the API is read only.
No files: no contracts, PDFs, riders or attachments.
No shared shows from other organisations. You only see what belongs to your organisation.