Docs sections
Orders
An order issues tickets. Send it in one request after payment, or hold the tickets with a reservation while the buyer pays.
There are two ways to sell a ticket.
- One request after payment. The buyer paid on your site, your site sends
POST /v1/ordersand gets the tickets. This fits when there are plenty of tickets and you're ready to refund if they sell out while the buyer pays. - A reservation during payment. Your site reserves the tickets before payment (
POST /v1/reservations), the buyer pays, your site confirms the reservation (POST /v1/reservations/{id}/confirm). If payment fails, you cancel the reservation (DELETE /v1/reservations/{id}) or it expires on its own. Use this for assigned seats and for tiers that may sell out.
Either way, the tickets are issued and the guest's email goes out right after confirmation.
Order object
| Field | Type | Description |
|---|---|---|
id | string | Order UUID |
org_id | string | Organization UUID |
livemode | boolean | A live order or a test one |
event_id | string | Event UUID |
status | string | Status, see below |
buyer | object | Buyer: email, first_name, last_name, phone. Also the ticket holder |
items | array | Contents: {tariff_id, qty, seat}. Tickets without seats are grouped by tier, each seat is its own item with qty: 1 and seat (the seat id) |
hold_expires_at | string | When the reservation runs out, RFC 3339 |
payment_ref | string | Your payment number, if you passed one |
tickets | array | Tickets: {id, url, tariff_id, holder_first_name, holder_last_name, status, seat}. Empty until the tickets are issued |
seats | array | The order's seats {id, sector, row, number, label} in item order |
reason | string | Why the order can't be confirmed. seat_taken: a seat went to another order, refund the buyer |
source | string | Where the order came from: api, manual (issued by hand) or import (from a guest list) |
tickets[].url is the link to the ticket page: https://budu.events/t/…. You can show it to the buyer right away. More in tickets.
Statuses
| Status | Meaning |
|---|---|
reserved | Tickets are held until hold_expires_at, no payment yet |
confirming | Confirmation is in progress. The order can no longer be cancelled |
paid | The sale is recorded and tickets are being issued. This usually takes a fraction of a second |
issued | Tickets are issued and the email is sent |
expired | The reservation expired. Confirming is still possible if tickets are left |
cancelled | The reservation was cancelled |
failed | Confirmation failed: the contents didn't match the reservation. Create a new order |
An order in paid or issued never becomes expired or cancelled. You can undo a sale only ticket by ticket: POST /v1/tickets/{id}/cancel.
Create an order
POST/v1/orders
Reservation and confirmation in one request. Send it after payment.
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | yes | UUID of a published event |
items | array | yes | Items: {"tariff_id", "qty"} or {"seat"} for a seat. Up to 20 tickets in total |
buyer.email | string | yes | Email: the ticket email goes there |
buyer.first_name, buyer.last_name | string | yes | First and last name: printed on the ticket |
buyer.phone | string | no | Phone, stored with the order |
payment_ref | string | no | Your payment number, returned in the order |
Request
curl https://api.budu.events/v1/orders \
-H "Authorization: Bearer $BUDU_KEY" \
-H "Idempotency-Key: order-1042" \
-H "Content-Type: application/json" \
-d '{
"event_id": "0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60",
"items": [{"tariff_id": "0193e0b2-4a7d-7a21-8c44-6e2f9a0b3c71", "qty": 2}],
"buyer": {
"email": "maria@example.ru",
"first_name": "Мария",
"last_name": "Соколова"
},
"payment_ref": "pay-77812"
}'Response 201
{
"id": "0193e0c5-1f20-7c3e-b6a1-9d4e2f7a8b10",
"org_id": "0193d9a1-2b3c-7d4e-8f50-61728394a5b6",
"livemode": false,
"event_id": "0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60",
"status": "issued",
"buyer": {
"email": "maria@example.ru",
"first_name": "Мария",
"last_name": "Соколова",
"phone": ""
},
"items": [
{"tariff_id": "0193e0b2-4a7d-7a21-8c44-6e2f9a0b3c71", "qty": 2, "seat": ""}
],
"hold_expires_at": "2026-10-22T12:15:04Z",
"payment_ref": "pay-77812",
"tickets": [
{
"id": "0193e0c5-1f6b-7e02-a3c9-5b8d1e4f9f0a",
"url": "https://budu.events/t/Vw3kqP7nX2bLmR9tYc4sHa",
"tariff_id": "0193e0b2-4a7d-7a21-8c44-6e2f9a0b3c71",
"holder_first_name": "Мария",
"holder_last_name": "Соколова",
"status": "valid",
"seat": null
},
{
"id": "0193e0c5-1f6b-7e02-a3c9-5b8d1e4fa21c",
"url": "https://budu.events/t/9pLmZ2xQw8RkT4vNb6CyJe",
"tariff_id": "0193e0b2-4a7d-7a21-8c44-6e2f9a0b3c71",
"holder_first_name": "Мария",
"holder_last_name": "Соколова",
"status": "valid",
"seat": null
}
],
"seats": [],
"reason": "",
"source": "api"
}If the tickets take longer than a couple of seconds to issue, the response is 202:
Response 202
{"status": "processing", "order_id": "0193e0c5-1f20-7c3e-b6a1-9d4e2f7a8b10"}The sale is already recorded, the tickets get issued on their own and the email goes out as usual. The ticket links appear in GET /v1/orders/{id}: read the order once a second until the status turns issued.
| Error | When |
|---|---|
400 invalid_request | No buyer, a bad email, no first or last name, empty contents, no such tier (details.tariff_id) |
404 not_found | No such event or seat |
409 event_not_on_sale | The event isn't published, or sales are still opening after publishing |
409 sold_out | The tier is sold out (details.tariff_id) |
409 seat_taken | The seat is already taken (details.seat_id, details.seat) |
422 limit_exceeded | More than 20 tickets, or more than the tier's per_order_limit (details.tariff_id) |
500 internal with details.order_id | The connection to the stock store broke off in the middle of the sale. We finish the order ourselves: check it with GET /v1/orders/{id} |
Reserve
POST/v1/reservations
The same fields as an order, plus one more:
| Field | Type | Required | Description |
|---|---|---|---|
hold_ttl_seconds | integer | no | How long to hold the reservation, 60 to 1,800 seconds. 900 by default, that is 15 minutes |
A reservation doesn't need payment_ref: you pass it when you confirm.
Request
curl https://api.budu.events/v1/reservations \
-H "Authorization: Bearer $BUDU_KEY" \
-H "Idempotency-Key: cart-5531" \
-H "Content-Type: application/json" \
-d '{
"event_id": "0193e0b2-4a7c-7d10-9e3f-2c5a8b1d4e60",
"items": [{"seat": "П-1-1"}, {"seat": "П-1-2"}],
"buyer": {
"email": "anna@example.ru",
"first_name": "Анна",
"last_name": "Петрова"
},
"hold_ttl_seconds": 600
}'You get 201 with the order in reserved status and hold_expires_at. While the reservation lasts, nobody else can buy these tickets and seats. The errors are the same as for an order.
Confirm a reservation
POST/v1/reservations/{id}/confirm
Call it after payment. The body is optional: {"payment_ref": "pay-77813"}.
Request
curl https://api.budu.events/v1/reservations/0193e0c5-2a40-7b11-9c3d-4e5f6a7b8c90/confirm \
-H "Authorization: Bearer $BUDU_KEY" \
-H "Idempotency-Key: pay-77813" \
-H "Content-Type: application/json" \
-d '{"payment_ref": "pay-77813"}'| Situation | Response |
|---|---|
| The reservation is alive, tickets are issued | 200, the order in issued status with tickets |
| The sale is recorded, tickets are still being issued | 202 {"status": "processing", "order_id"}. The tickets appear on their own |
| The reservation expired, but tickets are still left | the hold is taken again, then as in the first two rows |
| The reservation expired and the tickets are gone | 409 reservation_expired: refund the buyer |
| A seat from the order went to another order | 409 seat_taken with details.order_id, details.seat_id, details.seat. This is final: the order becomes expired with reason: "seat_taken", refund the buyer |
| The reservation was cancelled | 400 invalid_request «Резерв отменён — создайте новый» (reservation cancelled, create a new one) |
The order is already confirmed with a different payment_ref | 409 idempotency_conflict, the earlier payment in details.payment_ref |
Confirming is safe to repeat: a repeat returns the same order and the same tickets.
A successful confirm wins. If two confirms, or a confirm and the reservation's expiry, overlap and the sale got recorded, the order becomes paid and the tickets get issued. We don't roll back a sale. So don't cancel the payment because your HTTP client timed out: repeat the confirm with the same Idempotency-Key or read the order. 409 reservation_expired and 409 seat_taken come only when the sale didn't happen.
Cancel a reservation
DELETE/v1/reservations/{id}
Frees the reservation's tickets and seats when payment fails. A confirmed order can't be cancelled this way.
Request
curl -X DELETE https://api.budu.events/v1/reservations/0193e0c5-2a40-7b11-9c3d-4e5f6a7b8c90 \
-H "Authorization: Bearer $BUDU_KEY"You get 200 with the order in cancelled status. Cancelling again answers the same. Errors: 400 invalid_request «Заказ уже оплачивается — отменить резерв нельзя» (payment is in progress, status confirming) and «Заказ уже оплачен — отменяйте билеты по отдельности» (already paid, cancel tickets one by one; paid, issued).
Get an order
GET/v1/orders/{id}
Request
curl https://api.budu.events/v1/orders/0193e0c5-1f20-7c3e-b6a1-9d4e2f7a8b10 \
-H "Authorization: Bearer $BUDU_KEY"You get 200 with the order. This is always 200, even while tickets are being issued: then the status is paid and tickets doesn't hold every ticket yet.