Skip to content
FreeTicketDocs
Esc
↑↓navigate↵open⌘Jpreview

Create a B2C order and get the payment link (Mercado Pago)

Public for general admission (no auth, strict per-IP rate limit); orders with numbered seats REQUIRE a buyer session. Creates a PENDING sale + stock reservation and returns the Mercado Pago checkoutUrl to send the buyer to. The reservation holds the inventory for the payment window configured for that date (30 minutes unless the organizer sets it shorter or longer; never less than 10). The response includes expiresAt: the EXACT instant (ISO 8601, UTC) at which that reservation expires, the same one stored server-side. This is the value that decides whether it still makes sense to send the buyer to pay: past that instant the inventory has already returned to stock —and, for numbered seating, the seat is already on someone else’s map— so the checkoutUrl may still open and the payment may arrive late. Do not recompute it by adding minutes on the client: use the returned value. GET /orders/{id} repeats it while the order is still pending (and returns null once it no longer is), so a countdown can resume after a reload instead of restarting. The agent NEVER processes the payment. Not available for members-only tickets (members_only); PUBLISHED events with sales open, all from a single organizer. Inventory is protected by a Serializable transaction. NUMBERED ticket types (seats.io): items[].seats is required, with one EXACT label per unit (taken from availableSeats in GET /events/{slug}/availability) — the seat is held in seats.io with the same TTL as the stock reservation, and is released automatically if the order expires unpaid. An order with items[].seats additionally requires a BUYER SESSION: the ft-customer cookie or the session_token from the code login (POST /api/v1/customer/auth/request + POST /api/v1/customer/auth/verify) in the X-Customer-Session header; without it the response is 401 CUSTOMER_SESSION_REQUIRED. A seats.io hold can only be released by the sale that took it, so a seat held anonymously is inventory nobody can reclaim. The order is linked to that account: it shows up in GET /api/v1/customer/orders and its tickets in /api/v1/customer/tickets. General admission (no seats) remains anonymous and is not linked to any account. Dates with an active virtual queue: 409 QUEUE_REQUIRED (with details.waitingRoomUrl) — the waiting room is the only way in; the gate runs BEFORE the seats.io retrieve/hold. Check the status with GET /orders/{id}.

409 errors (error.code): MEMBERS_ONLY (members-only ticket), NOT_AVAILABLE (event or date not published, or date already past), EXTERNAL_SALE (the event sells elsewhere), SALE_CLOSED (outside the sales window), QUEUE_REQUIRED (the date is sold through a waiting room: details.waitingRoomUrl is the ABSOLUTE URL to send the buyer to — it points to the host where THAT date is sold, which may be the portal (/eventos/{slug}/fila) or the organizer’s or artist’s own subdomain (/events/{slug}/fila); use it as-is, do not rebuild it), MULTIPLE_ORGS (tickets from multiple organizers), MIXED_CURRENCY (mixed currencies), SEAT_NOT_AVAILABLE (seat already sold or held), ADDON_SOLD_OUT (a product bundled with the ticket, e.g. a drink combo, sold out: the order is not created). Products bundled with a ticket travel with it automatically —they are not requested in items— and their price is already included in total.

POST/orders
Authorization

Optional — this operation also accepts unauthenticated requests.

X-Customer-SessionAPI key · header

B2C buyer session. Obtained via POST /api/v1/customer/auth/request (code sent by email) + POST /api/v1/customer/auth/verify, which returns session_token. A browser can present the ft-customer cookie instead. Only orders with numbered seats require it (POST /orders with items[].seats); the rest of the contract is anonymous.

Request body
requiredapplication/json
buyerEmailstring<email>required
matches ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
buyerNamestringrequired
min length 1
buyerPhonestring
itemsobject[]required
min items 1
Show properties
Array of object
ticketTypeIdstringrequired
quantityintegerrequired
max 50
seatsstring[]
Responses
201

OK

dataOrderCreatedrequired
Show properties
orderIdstringrequired
referencestringrequired
statusstringrequired
Allowed:pendingpaidexpiredcancelled
totalnumberrequired
currencystringrequired
checkoutUrlstringrequired
expiresAtstringrequired
401

Missing or invalid credential.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
403

Insufficient role or resource not accessible.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
404

Resource does not exist or is out of scope.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
409

Conflict: the resource does not allow this operation in its current state.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
422

Operation error.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
429

Too many requests: rate limit exceeded.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
502

The external provider failed to complete the operation.

errorobjectrequired
Show properties
codestringrequired
messagestringrequired
retryablebooleanrequired
detailsany
Try it
Server
Authorization (optional)
Bodyapplication/json
Request
curl -X POST 'https://appfreeticket.com/api/public/orders' \
  -H 'X-Customer-Session: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "buyerEmail": "user@example.com",
  "buyerName": "string",
  "buyerPhone": "string",
  "items": [
    {
      "ticketTypeId": "string",
      "quantity": 1,
      "seats": [
        "string"
      ]
    }
  ]
}'
Response
{
  "data": {
    "orderId": "string",
    "reference": "string",
    "status": "pending",
    "total": 0,
    "currency": "string",
    "checkoutUrl": "string",
    "expiresAt": "string"
  }
}