Skip to content
Developer guide

Season ticket API: build a season end to end.

A season ticket API lets your own site or app sell one seat across many dates, then renew it next year. Ours is the same public API our ticket pages run on. Follow eight steps, from the first date to the next renewal.

Public docs and test mode. No partner approval.

What a season is made of.

Six words you will meet in the docs. This is an event ticketing API for reserved seats, not a help desk API.

Season
The whole product: a set of dates on one seat map, sold as one thing.
Plan
What a buyer gets inside a season: which dates, at which prices.
Holder
Your customer, known by your own holder reference, so you keep your login.
Seat right
A holder's claim on a seat, kept for them until a renewal deadline.
Renewal offer
A time-limited offer to keep that seat next season.
Amendment
A recorded change to a date: moved, postponed, added or cancelled.

Our checkout, or yours?

This one choice sets who owns payment, tickets and refunds. Most teams pick our checkout and build their own pages around it.

Who owns thisWith our checkoutWith your checkout
Customer login and customer pagesYouYou
Seats, holds and same-seat rulesSeatLayerSeatLayer
Season plans, renewals and seat rightsSeatLayerSeatLayer
PaymentSeatLayer checkout, paid to your gatewayYou
Orders and tickets with a QRSeatLayerYou
Send a game, give back a gameSeatLayerYou. These calls answer 409 own_checkout
RefundsSeatLayerYou
Door scanningSeatLayerYou

Build a season, step by step.

Every call below is public, with an idempotency key on anything that changes data.

  1. Step 1

    Make the dates

    Create every date in one call. Add dryRun to preview them first. It runs as a job, and each date stays a normal event with its own key, so anything you built on events keeps working.

    Event series
    POST /v1/event-seriesIdempotency-Key: 4c1e...{  "name": "Home games 2026/27",  "chartKey": "chart_wrenmoor",  "schedule": { "rule": "weekly", "from": "2026-08-15", "until": "2027-05-15", "times": ["15:00"] },  "dryRun": true} # Webhook when the job ends{ "type": "series.created" }
  2. Step 2

    Create the season

    A season links the dates, and a plan sets who gets what. Activate it, publish the plan, then open sales. Each step sends the revision it read. Test mode runs the same calls with no real money.

    Seasons API
    POST /v1/seasonsIdempotency-Key: season-2627{  "name": "Season 2026/27",  "edition": "2026/27",  "eventKeys": ["ev_calder", "ev_northport"]} POST /v1/seasons/sea_96bd/activate{ "expectedRevision": 1 } POST /v1/seasons/sea_96bd/plans/spl_12/publish{ "expectedRevision": 2 } POST /v1/seasons/sea_96bd/sales/open{ "expectedRevision": 3 }
  3. Step 3

    Sell on your page

    SeasonPicker holds the same seat on every date while the buyer pays. It ships for JavaScript, React, Vue, Angular, iOS, Android, React Native and Flutter, and Best Available finds seats together.

    SeasonPicker
    import { SeasonPicker } from "@seatlayer/js"; new SeasonPicker({  container: "#season",  season: "sea_96bd",  checkout: "hosted",}).render();
  4. Step 4

    Bring in holders

    Import last season's holders with a file-hash key, 100 rows a call. Check every row with a dry run, then commit. If chunk 37 fails, send it again and nothing doubles.

    Holder import
    POST /v1/seasons/sea_96bd/importsIdempotency-Key: file-9ac1-part-37{  "dryRun": false,  "successorPlanActivationId": "spa_51",  "rows": [{    "rowId": "row_1",    "holderRef": "club-customer-8821",    "labels": ["112-F-12"]  }]}
  5. Step 5

    Show their tickets

    A secret key acts for a holder reference, so you keep your own login. List their seasons, tickets and offers, and draw them in your own design.

    Holders API
    GET /v1/holders/club-customer-8821/seasonsGET /v1/holders/club-customer-8821/tickets # One ticket, with its QRGET /v1/holders/club-customer-8821/tickets/tkt_91ab
  6. Step 6

    Send or give back

    A holder can send one game to a friend, or give it back to the club. The ticket gets a new token, so the old QR is refused at the door, and the holder keeps their season.

    Transfers and returns
    POST /v1/holders/club-customer-8821/transfersIdempotency-Key: 7f3a...{ "ticketIds": ["tkt_91ab"], "toEmail": "sam.okafor@example.com" } POST /v1/holders/club-customer-8821/returns{ "ticketIds": ["tkt_92cd"] } # Webhook when Sam accepts{ "type": "season.ticket.transferred" }
  7. Step 7

    Renew

    Make the offers, send them your way, then inspect and commit on your own screens once you have taken payment. No hosted page needed.

    Renewals
    POST /v1/seasons/sea_2728/renewal-offers{ "successorPlanActivationId": "spa_51", "deadlineAt": 1812000000000 } # On your own renewal screenGET  /v1/seasons/sea_2728/renewal-offers/ofr_12/inspectPOST /v1/seasons/sea_2728/renewal-offers/ofr_12/commit{ "commitActionId": "renew-8821", "orderRef": "club-order-5521",  "bookingRef": "club-booking-5521", "planActivationId": "spa_51" } # A holder says noPOST /v1/seasons/sea_2728/renewal-offers/ofr_12/decline
  8. Step 8

    Change a date

    Move, postpone, add or cancel a date. Tickets follow, and webhooks tell your system. Bulk actions run as jobs, so a retry never pays or emails twice.

    Date changes
    POST /v1/seasons/sea_96bd/amendmentsIdempotency-Key: redmoor-moved{  "eventKey": "ev_redmoor",  "kind": "reschedule",  "startsAt": 1794950700000} # Webhook to every system that cares{ "type": "season.performance.rescheduled" }

How to build a ticketing system without starting from zero.

The screens are the easy part. The hard part sits underneath, and it is what you get from the API.

What a season needsBuild it from scratchBuild on SeatLayer
Seat holds that never double sellMonths of work to get rightDone. One hold per seat, on every date or none
Same seat across every dateYour own locking across eventsOne hold call
Renewals and seat rightsYour own data model and jobsOffers, deadlines and release built in
QR tickets, transfers and door checksYour own token and scanning systemReady with our checkout
Date changes that fix every ticketYour own bulk job engineOne call, run as a job
Seat map in 2D and 3DA design tool and a rendererDesigner and picker included

Four things to get right.

Every ticketing integration lives or dies on these.

  • Use an idempotency key

    Networks fail during big sales. Send a key with every call that changes data, and a retry never charges or emails twice.

    Idempotency-Key: season-2627
  • Handle a seat conflict

    Two buyers can want one seat. A 409 means it just went, so offer the next best seats.

    409 seats_unavailable
  • Listen to webhooks

    Don't poll. Signed webhooks tell you about sales, renewals, transfers and date changes, with retries.

    season.ticket.transferred season.performance.rescheduled season.bulk_action.completed
  • Decide who takes payment

    Use our checkout and we keep tickets and refunds. Use yours and you own them, and transfers answer 409.

    checkout: "hosted"

Don't want to build the pages?

Keep your own login and use our customer pages. One server call signs the customer in, and the browser never sees a secret.

Your server
# A handoff key can do nothing elsePOST /v1/customer-handoffsAuthorization: Bearer shk_live_8f2c...{  "holderRef": "club-customer-8821",  "email": "priya.shah@example.com",  "returnPath": "/games"} # 201. The link works once, for 60 seconds{ "url": "https://tickets.wrenmoorfc.example/account/handoff#t=fho_..." }

What developers ask first.

16 questions

Each answer stands alone, so people and AI answers can quote it directly.

The basics

A season ticket API lets your own site or app sell one seat across many dates and keep that holder for next year. It covers dates, plans, same-seat holds, holders, tickets, transfers, renewals and date changes. SeatLayer publishes its season ticket API as open docs.

No. The docs are public, and you can get test keys from a free account without a sales call.

Yes. You build the screens, and SeatLayer runs seats, holds, seasons and renewals. With our checkout it also runs payment pages, tickets and refunds.

It is an event ticketing API for reserved seats. It has nothing to do with IT support tickets.

With our checkout, $0.50 per seat per date. With your own checkout, you pay in SDK credits, and 100 sold seats are free every month. There is no subscription.

How it fits your product

Yes, for selling and renewing. With your own checkout you also own tickets, refunds and the door. Transfers and give backs need our checkout, because we hold the ticket.

Yes. A season runs on a published SeatLayer chart. You can draw it in the Designer, or embed the Designer in your own product.

Yes. The docs are public, with Markdown versions and an llms.txt file, so a coding agent can read the whole contract.

Yes. A secret key acts for your own holder reference, so you never move your customers into our login.

REST with OpenAPI covers every call. Seven server SDKs, for Node, Python, PHP, Java, Go, Ruby and .NET, carry the core season calls; holders and event series use REST. Season pickers ship for JavaScript, React, Vue, Angular, iOS, Android, React Native and Flutter.

Running it safely

Yes. Test keys run the same calls with no real money, and you can copy a tested setup to live.

One gets the hold and the other gets a 409. Your page can then offer the next best seats.

No seat sells twice: a season hold takes the same seat on every date at once, or none of them. Long actions, such as renewal runs and bulk refunds, run as jobs you can follow.

Send an idempotency key with every call that changes data. A retry returns the first result and does nothing twice.

Signed webhooks fire on every change that matters, with retries and a delivery log you can read.

Send them to the import call in chunks of 100 rows, with a dry run first. If a chunk fails, send it again and nothing doubles.

Didn't find your answer?

Keep reading

Run a test season today.

Get keys from a free account and sell your first season in test mode.