The REST API is available on the Professional plan and above. The base URL is https://triviyo.com/api/v1. The machine-readable OpenAPI 3 document describes every field.
Authentication
Create a key under Settings → API keys (owners and admins). Pick a name, the scopes it needs and, optionally, an expiry date. The full key (tvk_…) is shown once; Triviyo stores only a SHA-256 hash and the first characters, so a lost key has to be replaced.
Send the key on every request:
curl https://triviyo.com/api/v1/bookings?limit=10 \
-H "Authorization: Bearer $TRIVIYO_API_KEY"
The examples read the key from the TRIVIYO_API_KEY environment variable. The X-API-Key header also works. A key only ever sees the organization that created it. Revoking a key takes effect at once and turns off the webhook subscriptions it created. Keys stop working when the organization's subscription ends or drops below Professional.
| Scope | Allows |
|---|---|
experiences:read | GET /experiences, GET /experiences/{id}, GET /time-slots |
bookings:read | GET /bookings, GET /bookings/{id} |
bookings:write | POST /bookings |
customers:read | GET /customers |
customers:write | POST /customers |
webhooks:write | GET, POST /webhooks, DELETE /webhooks/{id} (subscribing also needs bookings:read, because events carry booking data) |
GET /me works with any key and returns the organization and the key's scopes.
Rate limits
Each key may make 60 requests per minute. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a throttled request gets 429 with Retry-After.
Errors
Errors use HTTP status codes and a JSON body:
{ "error": { "code": "invalid_request", "message": "The request is invalid.", "details": { "fieldErrors": {} } } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Bad JSON, unknown fields, failed validation, or a booking rule such as capacity or cut-off |
| 401 | unauthorized | Missing, invalid, expired or revoked key |
| 403 | forbidden | Missing scope, plan below Professional, or inactive subscription |
| 404 | not_found | The resource does not exist in your organization |
| 409 | conflict | For example too many webhook subscriptions |
| 429 | rate_limited | Over 60 requests per minute for this key |
Pagination
List endpoints return the newest items first: { "data": [...], "nextCursor": "..." }. Pass cursor=<nextCursor> to get the next page and limit (1–100, default 25) to set the page size. nextCursor is null on the last page.
Experiences and time slots
GET /experiences: active experiences with their active ticket types. AddincludeInactive=trueto include inactive ones.GET /experiences/{id}GET /time-slots?experienceId=&from=&to=&availableOnly=true: time slots betweenfromandto(ISO 8601; default the next 30 days, at most 92 days), ordered by start time.availableSpotsalready accounts for confirmed and pending bookings.
Money is returned as decimal strings with two decimals, for example "450.00", with the currency alongside.
Bookings
GET /bookings: filter withstatus,experienceId,bookingNumber,email,createdSinceandupdatedSince.GET /bookings/{id}POST /bookings: create a booking.
curl https://triviyo.com/api/v1/bookings \
-H "Authorization: Bearer $TRIVIYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"timeSlotId": "clx...",
"guest": { "email": "[email protected]", "firstName": "Kari", "lastName": "Nordmann" },
"tickets": [{ "ticketTypeId": "clx...", "quantity": 2 }]
}'
A booking made through the API goes through the same checks and prices as your booking page: capacity, booking cut-off, per-guest limits, pricing rules, time slot price overrides and discount codes. You cannot set prices or the status yourself. Free and pay-on-arrival bookings are confirmed at once and the guest gets the usual confirmation email, unless the experience requires your approval (then they stay PENDING until you approve); bookings for pay-now experiences are created as PENDING and expire like an unpaid web booking. The booking's source is API. A second booking for the same guest email and time slot is refused, so retrying a request cannot create a duplicate.
Customers
GET /customers: filter withemailandupdatedSince.POST /customers: create a customer, or update the customer with the same email (201when created,200when updated). Fields:email,firstName,lastName,phone,marketingConsent. Only setmarketingConsenttotruewhen the customer has agreed to marketing.
Webhook subscriptions
Integrations such as Zapier can subscribe to the webhook events themselves (REST hooks):
curl https://triviyo.com/api/v1/webhooks \
-H "Authorization: Bearer $TRIVIYO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/triviyo", "events": ["BOOKING_CREATED", "BOOKING_CANCELED"] }'
The response contains the subscription id and its signing secret (shown only here). Deliveries are signed with X-Triviyo-Signature, retried and disabled after repeated failures exactly like webhooks you add in Settings. DELETE /webhooks/{id} unsubscribes; a key can only list and delete its own subscriptions, and holds at most 25. The URL must be a public https address.