BlendPartner API
blendapp.ai

Getting started

OverviewQuickstartHow the integration worksAuthenticationAny backend

Catalog

Listing eventsEvent detail & ticketsWhich events you see

Checkout

Buyer sessionsInventory & seat holdsReserving an orderTaking the paymentConfirming & settlement

Mobile apps

Native UI (no webview)Embedding the webviewiOS (Swift)Android (Kotlin)React Native

Reference

ErrorsRate limitsTesting & go-live

Inventory & seat holds

Two buyers can tap “Buy” on the last ticket at the same instant. Only one of them can get it. This page explains how Blend makes that decision, for events with a simple ticket count and for events with a seat map, and, the part integrators most often get wrong, exactly when a ticket stops being available to everyone else.

A reserved order is not a guaranteed ticket

For paid orders, Blend claims inventory when the payment is confirmed, not when the order is reserved. Reserve tells you the price and gives you an orderId; confirm is the moment the ticket is actually taken. Read when inventory is claimed before designing your payment flow.

General admission: one atomic compare-and-set

A general admission (GA) ticket type has three counters: availableCount (the capacity; 0 means unlimited), soldCount, and heldCount (stock the organiser has held back and is not selling). A claim for quantity tickets succeeds only when:

The oversell guard
soldCount + quantity <= availableCount - heldCount

# availableCount of 0 means unlimited: the guard is skipped.

The important property is not the arithmetic; it is where it runs. The guard is part of the query of a single MongoDB findOneAndUpdate. The database evaluates the condition and applies the increment as one operation. There is no moment where Blend reads the count, decides, and then writes, no read-then-write window in which a second buyer can slip through.

If two buyers race for the last ticket, the database serialises the two updates. The first one satisfies the guard and increments soldCount. The second one is evaluated against the new value, fails the guard, and matches nothing. It cannot “also win”.

Multi-line carts

An order can hold up to 50 lines (for example three Standard and two VIP). Lines are claimed one at a time, each with its own atomic guard. If any line fails, every line already claimed for that order is rolled back, and the order fails as a whole. You never end up with a buyer who got their Standard tickets but not their VIP ones.

Optional Redis fast path for high-demand events

For on-sales that expect a large burst, Blend can put the counters in Redis ahead of MongoDB. Each ticket type has a key of the form inv:{eventId}:t:<ticketId> holding { cap, rem }, capacity and remaining. Every line in an order is decremented in one Lua script, which is all-or-nothing across the whole order: either every line had enough remaining and all of them are decremented, or nothing changes.

PropertyTypeDescription
Keystringinv:{eventId}:t:<ticketId>, holding cap and rem.
AtomicityLua scriptAll lines of the order succeed or fail together, in one script execution.
TTL30 daysRefreshed on each claim, so an event that keeps selling never loses its counters.
Fallbackfail openIf Redis is unavailable, or a key was never seeded for that ticket type, the claim goes to the MongoDB path described above.
Oversell is impossible on both paths

“Fail open” here means falling back to the slower path, not skipping the check. The MongoDB guard is always there underneath. Redis only makes the answer faster; it is never the sole line of defence.

When inventory is claimed

This is the counterintuitive part, so it is stated plainly: for a paid order, the counters above are not touched when you reserve. They are touched when you confirm.

  1. Reserve

    Blend validates the cart, prices it, and creates a pending order with an orderId and an amount. Nothing has been taken from the event's counts yet.

  2. You charge the buyer on your rail

    Toters Pay, or whatever you use. Blend is not involved in this step.

  3. Confirm

    Your backend calls POST /v1/channel/orders/confirm. This is when Blend runs the atomic claim. If it succeeds, the order becomes paid and the ticket is issued.

  4. If the claim fails at that moment

    That is a genuine race: someone else's confirm took the last ticket between your reserve and your confirm. Blend cancels the registration and the payment must be refunded. Because you took the payment, the refund is yours to make; the confirm endpoint tells you so explicitly with 409 CONFIRM_FAILED and refundRequired: true.

Design for this. Keep the time between reserve and confirm short, and make sure the code path that handles a confirm failure can issue a refund without a human in the loop.

Powered byBlend

Reserved seating: holds in Redis

Events with a seat map work differently. A specific seat cannot be “counted”; it is either free, held by someone who is deciding, or booked. While a buyer is choosing seats, Blend places a hold on them so nobody else can pick the same seat in the meantime.

Lifetimes

ValueTypeDescription
Total hold lifetime720 s600 seconds shown to the buyer plus 120 seconds of hidden grace, so a buyer who taps Pay at 9:59 is not cut off by a slow network.
expiresInSeconds600What the client is told, and what a countdown should display. Do not show the grace period.
One-time extension600 sAvailable once per hold, for a payment attempt. Use it when the buyer has committed and the charge is in progress.
Hold token16 bytes, hexIdentifies the hold. Keep it for the extend and release calls.

How a hold is taken

The hold is written by a single Lua script. Every requested seat must be free (or already held by your own token, re-holding your own seats is allowed) and must not be blocked by the organiser. If any seat fails that test, nothing is written at all. You either hold the whole selection or none of it, and no two buyers can hold the same seat.

Holds expire by Redis key TTL. There is no sweeper job that runs every minute and might lag behind; when the TTL is reached, the seat is free at that exact moment.

Seats become permanently booked only when the payment is confirmed, the same rule as general admission. A hold is a promise that nobody else will take the seat while the buyer decides, not a booking.

Seat endpoints inside a session

These run inside a buyer session (authenticated by the session cookie set when the session URL was opened). In the embedded checkout, Blend's own checkout page calls them; you do not need to call them yourself unless you are building a custom seat picker.

POST/v1/channel/session/events/:id/seats/config
POST/v1/channel/session/events/:id/seats/availability
POST/v1/channel/session/events/:id/seats/hold-token
POST/v1/channel/session/events/:id/seats/hold
POST/v1/channel/session/events/:id/seats/hold/:token/extend
POST/v1/channel/session/events/:id/seats/release
EndpointTypeDescription
seats/configreadThe seat map for the event.
seats/availabilityreadWhich seats are currently free, held, blocked or booked.
seats/hold-tokenwriteIssues a hold token (16 random bytes, hex) to use for subsequent hold calls.
seats/holdwriteTakes the hold, all-or-nothing, for the requested seats. Returns expiresInSeconds: 600.
seats/hold/:token/extendwriteThe one-time 600 second extension for a payment attempt.
seats/releasewriteReleases the hold. Works even for an event outside your catalog, releasing inventory is never blocked.
Hold response (excerpt)
{
  "expiresInSeconds": 600
}

// Only the timing field is shown here.
// The hold actually lives 720 seconds. The extra 120 are hidden grace
// so a buyer who pays at the last visible second is not cut off.
Release is deliberately permissive

seats/release succeeds even for an event outside your curated catalog. A buyer backing out must always be able to give the seats back; there is no situation where Blend benefits from refusing.

Related errors

These are returned by checkout when inventory or a hold does not cooperate.

CodeTypeDescription
TICKET_SOLD_OUT409The requested ticket type has no remaining capacity.
EVENT_SOLD_OUT409The event as a whole has no remaining capacity.
QUANTITY_EXCEEDS_CAPACITY409The requested quantity is more than the event can supply.
TICKET_QUANTITY_EXCEEDS_CAPACITY409The requested quantity is more than that ticket type can supply.
SEAT_CONFLICT409One or more requested seats are held or booked by someone else.
reason: "hold_expired"409The seat hold ran out before checkout. Take a new hold and try again.

Next

Reserving an orderQuote a price and reserve, with idempotency keys and the full error list.Confirming & settlementThe call that actually claims inventory and issues the ticket.Buyer sessionsThe session cookie that authenticates the seat endpoints.ErrorsEvery error code in one place.
Powered byBlend© 2026 Blend · Ticketing & event infrastructure