API reference · v1
A REST API for events, ticket inventory, checkout sessions and order status, with signed webhooks. Build against sandbox first. Test keys never move real money or reserve real tickets.
developers.ayatickets.com/api/v1
X-API-Key: ayt_test_...
Start with a sandbox key.
Events, then dates, then ticket types.
Send the customer to checkout_url.
All paths are relative to the base URL.
/events
List published events. Supports page, per_page and q.
GET
/events/{id}
Get one published event.
GET
/events/{id}/dates
List active dates. Add include_past for past dates.
GET
/event-dates/{id}/tickets
Ticket types with price and quantity available.
POST
/checkout-sessions
Create a checkout session. Needs Idempotency-Key.
GET
/orders/{reference}
Order status for orders your app created.
Send your secret key in the X-API-Key header on every request. Keys belong to one app. Keep them on your server, never in a browser or mobile app.
ayt_test_* keys hit the sandbox. Checkout is simulated, order references start with sbx_ and no money moves.ayt_live_* keys create real orders and hosted payments.environment_mismatch.429 with rate_limit_exceeded. Back off using Retry-After.Events marked hidden_from_listings in Ayatickets are intentionally excluded from every catalog and checkout endpoint. Partners cannot sell unlisted events through the API.
curl 'https://developers.ayatickets.com/api/v1/events' \
-H 'X-API-Key: ayt_test_...'
GET /events
Returns published events (<code>published: true</code>), newest first, with a pricing summary. Response <code>meta</code> includes <code>include_past</code> (always false on this route), pagination fields, and <code>timezone</code>. Checkout responses also echo the HTTP status in <code>meta.status_code</code>.
page integer Optionalper_page integer Optionalq string Optionalinclude_past boolean Optionalcurl 'https://developers.ayatickets.com/api/v1/events?per_page=2' \
-H 'X-API-Key: ayt_test_...'
{
"data": [
{
"id": 1933,
"reference": "dc107c7242",
"name": "Bombele Night",
"slug": "bombele-night",
"active_dates_count": 1,
"next_startdate_iso": "2026-12-19T19:30:00+00:00",
"pricing": {
"has_free_tickets": false,
"has_paid_tickets": true,
"is_entirely_free": false
}
}
],
"meta": {
"page": 1,
"per_page": 2,
"total": 38,
"has_next": true,
"timezone": "Africa/Accra"
}
}
GET /events/{id}
Returns one published event. Unpublished or missing events return 404 event_not_found.
id integer Requiredcurl 'https://developers.ayatickets.com/api/v1/events/1933' \
-H 'X-API-Key: ayt_test_...'
{
"data": {
"id": 1933,
"name": "Bombele Night",
"description": "Tickets on Ayatickets.",
"image_url": "https://cdn.ayatickets.com/uploads/events/65aae9be417d627270cb306f50d5f855.jpg",
"has_multiple_dates": false,
"timezone": "Africa/Accra"
}
}
GET /events/{id}/dates
Returns upcoming active dates for an event, with venue details. Pick one to fetch its tickets.
id integer Requiredinclude_past boolean Optionalcurl 'https://developers.ayatickets.com/api/v1/events/1933/dates' \
-H 'X-API-Key: ayt_test_...'
{
"data": [
{
"id": 5085,
"event_id": 1933,
"startdate_iso": "2026-12-19T19:30:00+00:00",
"online": false,
"venue_name": "ROYAL MARICAY",
"venue_city": "Tanoso"
}
],
"meta": {
"event_id": 1933,
"count": 1,
"include_past": false
}
}
GET /event-dates/{id}/tickets
Ticket types for one event date, with live price and quantity_available. The organizer <code>free</code> flag is a boolean; use <code>is_free</code>, <code>effective_price</code>, and <code>requires_payment</code> for checkout pricing (matches Ayatickets core <code>getSalePrice()</code>).
id integer Requiredcurl 'https://developers.ayatickets.com/api/v1/event-dates/5085/tickets' \
-H 'X-API-Key: ayt_test_...'
{
"data": [
{
"id": 508065,
"name": "Girls",
"currency": "GHS",
"effective_price": "25.00",
"quantity_available": 212,
"requires_payment": true
}
],
"meta": {
"event_id": 1933,
"event_date_id": 5085
}
}
POST /checkout-sessions
Creates a hosted checkout for one event date. We work out prices and check stock on our side, so never send a price or total. Live free carts complete immediately with status: completed, requires_payment: false, and checkout_url: null (webhook order.completed). customer.phone must be a valid Ghana mobile number for live free checkout. Paid live carts return status: pending with a hosted checkout_url.
event_id integer Requiredeventdate_id integer Requireditems array Requiredcustomer object Requiredsuccess_url, cancel_url string Requiredmetadata object OptionalIdempotency-Key is required. Same key and same body returns the saved response. Same key with a different body returns 409 idempotency_conflict. Keys are kept for 60 minutes.
Redirect allowlist. success_url and cancel_url must be HTTPS and their host must be on your app's allowed redirect domains. Add domains on the app page in the developer portal.
curl -X POST 'https://developers.ayatickets.com/api/v1/checkout-sessions' \
-H 'X-API-Key: ayt_test_...' \
-H 'Idempotency-Key: checkout-8f2b' \
-H 'Content-Type: application/json' \
-d '{
"event_id": 1933,
"eventdate_id": 5085,
"items": [{ "ticket_id": 508065, "quantity": 2 }],
"customer": { "name": "Kwame Mensah", "email": "kwame@company.com", "phone": "233200000000" },
"success_url": "https://merchant.com/success",
"cancel_url": "https://merchant.com/cancel"
}'
const res = await fetch('https://developers.ayatickets.com/api/v1/checkout-sessions', {
method: 'POST',
headers: {
'X-API-Key': process.env.AYATICKETS_API_KEY,
'Idempotency-Key': 'checkout-8f2b',
'Content-Type': 'application/json',
},
body: JSON.stringify({
event_id: 1933,
eventdate_id: 5085,
items: [{ ticket_id: 508065, quantity: 2 }],
customer: { name: 'Kwame Mensah', email: 'kwame@company.com', phone: '233200000000' },
success_url: 'https://merchant.com/success',
cancel_url: 'https://merchant.com/cancel',
}),
});
const session = await res.json();
// Redirect the buyer to session.checkout_url
$response = Http::withHeaders([
'X-API-Key' => env('AYATICKETS_API_KEY'),
'Idempotency-Key' => 'checkout-8f2b',
])->post('https://developers.ayatickets.com/api/v1/checkout-sessions', [
'event_id' => 1933,
'eventdate_id' => 5085,
'items' => [['ticket_id' => 508065, 'quantity' => 2]],
'customer' => ['name' => 'Kwame Mensah', 'email' => 'kwame@company.com', 'phone' => '233200000000'],
'success_url' => 'https://merchant.com/success',
'cancel_url' => 'https://merchant.com/cancel',
]);
return redirect($response->json('checkout_url'));
{
"order_reference": "ORD-2026-00124",
"checkout_url": "https://checkout.ayatickets.com/session/...",
"expires_at": "2026-03-15T12:05:00+00:00",
"status": "pending",
"environment": "live",
"requires_payment": true,
"total_amount": "50.00"
}
In sandbox, checkout simulates success, failure and cancel. Confirm payment with a webhook or GET /orders/{reference}, never from the redirect alone.
GET /orders/{reference}
Status for an order your app created. Use it with webhooks as the source of truth before you fulfil an order. <code>data.status</code> is <code>pending</code>, <code>paid</code>, <code>completed</code> (live developer free orders), <code>failed</code>, <code>expired</code>, or <code>unknown</code>. Sandbox free checkouts may still report <code>paid</code>.
reference string Requiredcurl 'https://developers.ayatickets.com/api/v1/orders/ORD-2026-00124' \
-H 'X-API-Key: ayt_test_...'
{
"data": {
"reference": "ORD-2026-00124",
"status": "paid",
"currency": "GHS",
"payment_reference": "AYT-PAY-88213",
"environment": "live"
}
}
Treat webhooks and order status as the source of truth for fulfilment, not the redirect.
order.paid order.completed (live free checkout) order.failed order.expired webhook.testX-AYT-Webhook-Timestamp, X-AYT-Webhook-Signature, X-AYT-Event-Id, X-AYT-Event-TypeX-AYT-Event-Id to ignore duplicates.const signed = timestamp + "." + rawBody;
const expected = crypto
.createHmac("sha256", webhookSecret)
.update(signed)
.digest("base64");
// Compare in constant time.
// Reject if the timestamp is over 5 minutes old.
The signature is base64(HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)). Use the raw request body, before any JSON parsing.
Every error has the same shape: an error object with code, message and, for validation, details.
missing_api_key
GET/POST without X-API-Key (counts toward failed-auth IP bucket).
missing_idempotency_key
Checkout request sent without Idempotency-Key.
invalid_idempotency_key
Idempotency-Key header is too long.
invalid_api_key
API key missing, malformed or revoked.
unauthorized
Authenticated app context missing on an internal route.
app_suspended
The app is suspended.
account_disabled
The developer account is disabled.
account_pending_approval
Your account is waiting for approval.
environment_mismatch
Key environment does not match the order (including GET /orders).
event_not_available
Event is private, password-protected, or otherwise unavailable for checkout.
event_not_found
Event does not exist, is not published, or is hidden from listings.
event_date_not_found
Event date does not exist or is not active.
not_found
Unknown API route.
order_not_found
Order is not linked to your app.
method_not_allowed
HTTP method is not allowed for this endpoint.
idempotency_conflict
Same key used with a different payload.
idempotency_in_progress
The first request with this key is still running.
sold_out
Ticket inventory is exhausted.
validation_failed
Payload failed validation (fields, types, URLs, max 20 items).
event_date_not_available
Event date is inactive, ended, or payout-locked.
ticket_not_found
Ticket is invalid for the event date.
ticket_not_on_sale
Ticket or event sales window has not started or has ended.
ticket_quantity_exceeded
Quantity exceeds tickets-per-attendee limit.
invalid_redirect_url
success_url or cancel_url is not a valid HTTPS URL.
redirect_allowlist_required
App has no allowed redirect domains.
redirect_domain_not_allowed
success_url or cancel_url host is not allowed.
metadata_too_large
Metadata over 20 keys or 4 KB.
rate_limit_exceeded
Too many failed-auth attempts per IP or too many requests per app. Retry-After header is set.
quantity_exceeds_limit
First live free order for this buyer/event exceeds the per-buyer daily ticket cap (UTC day boundaries).
free_order_limit_reached
Daily live free ticket cap reached for this app or buyer/event (UTC day boundaries).
checkout_unavailable
Checkout is temporarily unavailable. Retry, then contact support.
free_checkout_unavailable
Live free checkout kill switch is off.
payment_gateway_unavailable
Free payment gateway unavailable (live free checkout).
internal_error
Unexpected server error (non-debug environments).
{
"error": {
"code": "validation_failed",
"message": "Request validation failed.",
"details": { "items.0.quantity": ["The quantity must be at least 1."] }
}
}
We store only a hash of each key. Revoke a leaked key from the app page and create a new one.
Buyers enter payment details on hosted checkout. They never touch your servers.
Customer name, email and phone are used only to fulfil tickets. Keep personal data out of metadata. Retention or deletion requests: developers@ayatickets.com.