API reference
POST /checkout creates a ticket payment and returns a payment link: the required Idempotency-Key, order status via GET /orders and common errors.
POST/checkout · scope checkout:write
Creates a payment and returns a payment link. Send the buyer to that link, then watch the order status. After a successful payment we send the ticket to the email address given.
curl -X POST https://aoa.com.ua/api/v1/checkout \
-H "Authorization: Bearer aoa_live_…" \
-H "Idempotency-Key: order-2026-07-30-a1b2c3-0042" \
-H "Content-Type: application/json" \
-d '{
"eventId": "a1b2c3",
"tickets": [
{ "ticketTypeId": "clx9ticket001", "quantity": 2 }
],
"buyer": {
"email": "ivan@example.com",
"name": "Іван Петренко",
"phone": "+380671234567"
},
"reservationId": "rsv_Xk3mQp7vR2nT8wL5yB1zC4dA"
}'| Field | Required | Description |
|---|---|---|
eventId | Yes | Event identifier. |
tickets | Yes | Items with ticketTypeId and quantity (1–20 per type). |
buyer.email | Yes | The ticket goes here. Validate the address on your side. |
buyer.name | Yes | Buyer’s name. |
buyer.phone | No | Buyer’s phone number. |
couponCode | No | Promo code. A code that doesn’t exist or has expired returns an error, not the full price. |
reservationId | No | A reservation from /reservations. Its seats move into the payment. |
The Idempotency-Key header (at least 8 characters) protects against double charges if the network drops and you don’t know whether the request got through. A repeat request with the same key returns the same response instead of a second payment:
{
"data": {
"paymentId": "d3f1a2b4-7c8e-4f10-9a2b-5c6d7e8f9012",
"paymentUrl": "https://pay.mbnk.biz/240730a1b2c3"
},
"meta": { "idempotent": true }
}Keys are remembered for 24 hours and kept separate per partner, so your key can’t collide with anyone else’s. Use a value unique to the order, for example the cart identifier in your system.
The key wins over the request body
A repeat with the sameIdempotency-Key but a different cart returns the old response, without warning. A new order always needs a new key.{
"data": {
"paymentId": "d3f1a2b4-7c8e-4f10-9a2b-5c6d7e8f9012",
"paymentUrl": "https://pay.mbnk.biz/240730a1b2c3"
}
}paymentUrl is the payment page to redirect the buyer to. Keep paymentId: you check the status with it.
This request is noticeably slower than the others: we create the invoice with the bank synchronously so we can return a ready link. Set a timeout of at least 15 seconds.
GET/orders/{paymentId} · scope checkout:write
{
"data": {
"paymentId": "d3f1a2b4-7c8e-4f10-9a2b-5c6d7e8f9012",
"eventId": "a1b2c3",
"status": "SUCCESS",
"amountMinor": 90000,
"amountDecimal": "900.00",
"currency": "UAH",
"attendeeId": "clx9attendee01",
"failureReason": null,
"createdAt": "2026-07-30T12:00:00.000Z",
"paidAt": "2026-07-30T12:02:31.000Z"
}
}| status | Meaning |
|---|---|
PENDING | Created, the buyer hasn’t paid yet |
PROCESSING | The bank is processing the payment |
SUCCESS | Paid, the ticket has been sent to the buyer |
FAILED | The payment didn’t go through |
EXPIRED | The buyer didn’t pay in time, the seats were released |
REFUNDED | The money was refunded |
The attendeeId field is only filled in after a successful payment; until then it’s null.
Poll the status every 5–10 seconds and stop at any status other than PENDING and PROCESSING. Instead of polling, you can subscribe to the webhooks order.paid, order.failed and order.refunded: AOA will tell you the result itself.
You only see your own orders
The API only gives access to payments created with your key. Someone else’s or a non-existentpaymentId returns 404.| Situation | What to do |
|---|---|
409 “Payments are not configured for this event” | The organizer hasn’t connected payments. Mark the event as unavailable for sale: retrying makes no sense. |
409 about tickets left | The seats have been taken. Refresh the numbers and offer a smaller quantity. |
400 about a free event | The event has no paid tickets: people register for it on the website, not through payment. |
400 without Idempotency-Key | Add the header, at least 8 characters long. |
The full list of codes is on the Errors page.