Skip to content

Install a server SDK

Official SeatLayer server SDKs for Node.js, Python, PHP, Java, Go, Ruby and .NET, with idempotency, retries and typed errors built in.

Updated View as Markdown

The server SDKs wrap the server API with the parts every backend integration ends up writing anyway: idempotency keys that survive retries, backoff that honours our rate limits, errors typed by what you actually branch on, and constant-time webhook verification.

Install

npm install @seatlayer/server

Requires Node 20.19.4 or newer. No runtime dependencies.

Create a clientts
import { SeatLayer } from '@seatlayer/server';

const seatlayer = new SeatLayer(process.env.SEATLAYER_SECRET_KEY!);
pip install seatlayer

Requires Python 3.10 or newer. No runtime dependencies.

Create a clientpython
import os
from seatlayer import SeatLayer

seatlayer = SeatLayer(os.environ["SEATLAYER_SECRET_KEY"])
composer require seatlayer/seatlayer-php

Requires PHP 8.1 or newer with ext-curl. No Composer dependencies.

Create a clientphp
use SeatLayer\SeatLayer;

$seatlayer = new SeatLayer(getenv('SEATLAYER_SECRET_KEY'));
<dependency>
  <groupId>io.seatlayer</groupId>
  <artifactId>seatlayer-java</artifactId>
  <version>0.1.0</version>
</dependency>

Requires Java 17 or newer. Zero runtime dependencies — JDK HTTP and crypto only.

Create a clientjava
import io.seatlayer.SeatLayer;

SeatLayer seatlayer = new SeatLayer(System.getenv("SEATLAYER_SECRET_KEY"));
go get github.com/seatlayer/seatlayer-go

Requires Go 1.23 or newer. No dependencies — standard library only.

Create a clientgo
import "github.com/seatlayer/seatlayer-go"

client, err := seatlayer.New(os.Getenv("SEATLAYER_SECRET_KEY"))
gem install seatlayer

Requires Ruby 3.0 or newer. No runtime dependencies.

Create a clientruby
require "seatlayer"

client = SeatLayer::Client.new(ENV.fetch("SEATLAYER_SECRET_KEY"))
dotnet add package SeatLayer

Requires .NET 8 or newer. No package dependencies.

Create a clientcsharp
using SeatLayer;

var client = new SeatLayerClient(Environment.GetEnvironmentVariable("SEATLAYER_SECRET_KEY")!);

Your first sale

The shape below is the whole product in five calls: provision a venue, put an event on it, and sell four seats without a browser in the loop.

// 1. Provision a venue for a new organiser from one of your templates.
const { meta: chart } = await seatlayer.charts.copy('c_template_arena');
await seatlayer.charts.publish(chart.id);

// 2. Create an event on it.
const { meta: event } = await seatlayer.events.create({
  chartId: chart.id,
  name: 'Spring Gala',
});

// 3. Sell four seats over the phone.
const held = await seatlayer.inventory.holdBestAvailable(event.key, { qty: 4 });
// … take payment against held.items, which carry authoritative prices …
await seatlayer.inventory.book(event.key, {
  holdId: held.holdId,
  bookingRef: 'order-8842',
});
# 1. Provision a venue for a new organiser from one of your templates.
chart = seatlayer.charts.copy("c_template_arena")["meta"]
seatlayer.charts.publish(chart["id"])

# 2. Create an event on it.
event = seatlayer.events.create(chart_id=chart["id"], name="Spring Gala")["meta"]

# 3. Sell four seats over the phone.
held = seatlayer.inventory.hold_best_available(event["key"], qty=4)
# … take payment against held["items"], which carry authoritative prices …
seatlayer.inventory.book(
    event["key"], hold_id=held["holdId"], booking_ref="order-8842"
)
// 1. Provision a venue for a new organiser from one of your templates.
$chart = $seatlayer->charts->copy('c_template_arena')['meta'];
$seatlayer->charts->publish($chart['id']);

// 2. Create an event on it.
$event = $seatlayer->events->create($chart['id'], name: 'Spring Gala')['meta'];

// 3. Sell four seats over the phone.
$held = $seatlayer->inventory->holdBestAvailable($event['key'], qty: 4);
// … take payment against $held['items'], which carry authoritative prices …
$seatlayer->inventory->book($event['key'], holdId: $held['holdId'], bookingRef: 'order-8842');
// 1. Provision a venue for a new organiser from one of your templates.
var chart = (Map<String, Object>) seatlayer.charts().copy("c_template_arena").get("meta");
seatlayer.charts().publish((String) chart.get("id"));

// 2. Create an event on it.
var event = (Map<String, Object>) seatlayer.events()
    .create((String) chart.get("id"), "Spring Gala").get("meta");

// 3. Sell four seats over the phone.
var held = seatlayer.inventory().holdBestAvailable((String) event.get("key"), 4);
// … take payment against held.get("items"), which carry authoritative prices …
seatlayer.inventory().book((String) event.get("key"), (String) held.get("holdId"), "order-8842");
ctx := context.Background()

// 1. Provision a venue for a new organiser from one of your templates.
chart, err := client.Charts.Copy(ctx, "c_template_arena")
chartID := chart["meta"].(map[string]any)["id"].(string)
client.Charts.Publish(ctx, chartID)

// 2. Create an event on it.
event, err := client.Events.Create(ctx, seatlayer.EventCreateParams{
    ChartID: chartID, Name: "Spring Gala",
})
eventKey := event["meta"].(map[string]any)["key"].(string)

// 3. Sell four seats over the phone.
held, err := client.Inventory.HoldBestAvailable(ctx, eventKey,
    seatlayer.BestAvailableParams{Qty: 4})
// … take payment against held["items"], which carry authoritative prices …
client.Inventory.Book(ctx, eventKey, seatlayer.BookParams{
    HoldID: held["holdId"].(string), BookingRef: "order-8842",
})
# 1. Provision a venue for a new organiser from one of your templates.
chart = client.charts.copy("c_template_arena")["meta"]
client.charts.publish(chart["id"])

# 2. Create an event on it.
event = client.events.create(chart_id: chart["id"], name: "Spring Gala")["meta"]

# 3. Sell four seats over the phone.
held = client.inventory.hold_best_available(event["key"], qty: 4)
# … take payment against held["items"], which carry authoritative prices …
client.inventory.book(event["key"], hold_id: held["holdId"], booking_ref: "order-8842")
// 1. Provision a venue for a new organiser from one of your templates.
var chart = (IReadOnlyDictionary<string, object?>)(await client.Charts.CopyAsync("c_template_arena"))["meta"]!;
await client.Charts.PublishAsync((string)chart["id"]!);

// 2. Create an event on it.
var created = await client.Events.CreateAsync((string)chart["id"]!, name: "Spring Gala");
var eventKey = (string)((IReadOnlyDictionary<string, object?>)created["meta"]!)["key"]!;

// 3. Sell four seats over the phone.
var held = await client.Inventory.HoldBestAvailableAsync(eventKey, new BestAvailableRequest { Qty = 4 });
// … take payment against held["items"], which carry authoritative prices …
await client.Inventory.BookAsync(eventKey, (string)held["holdId"]!, bookingRef: "order-8842");

Test and live keys

A key carries its own mode. sk_test_… keys can only act on test-mode events and sk_live_… only on live ones; crossing them answers 403 mode_mismatch. The client exposes its mode so you can refuse to boot the wrong way round.

if (process.env.NODE_ENV === 'production' && seatlayer.mode !== 'live') {
  throw new Error('Refusing to boot production against test-mode seating data.');
}
if os.environ.get("ENV") == "production" and seatlayer.mode != "live":
    raise RuntimeError("Refusing to boot production against test-mode seating data.")
if (getenv('APP_ENV') === 'production' && $seatlayer->mode !== 'live') {
    throw new RuntimeException('Refusing to boot production against test-mode seating data.');
}
if ("production".equals(System.getenv("ENV")) && !"live".equals(seatlayer.mode())) {
    throw new IllegalStateException("Refusing to boot production against test-mode seating data.");
}
if os.Getenv("ENV") == "production" && client.Mode() != "live" {
    return errors.New("refusing to boot production against test-mode seating data")
}
raise "Refusing to boot production against test-mode seating data." if
  ENV["RAILS_ENV"] == "production" && client.mode != "live"
if (env.IsProduction() && client.Mode != "live")
{
    throw new InvalidOperationException("Refusing to boot production against test-mode seating data.");
}

A publishable pk_ key passed here is rejected at construction with a message naming the mistake, rather than failing as a 401 three round-trips later.

Listing large accounts

list() returns one page plus a nextCursor. When you want everything, the listAll helpers page for you and yield as they go — an iterator rather than an array, because the point of paginating is to not hold an unbounded list in memory.

for await (const event of seatlayer.events.listAll()) {
  await sync(event);
}
for event in seatlayer.events.list_all():
    sync(event)
foreach ($seatlayer->events->listAll() as $event) {
    sync($event);
}
for (Map<String, Object> event : seatlayer.events().listAll()) {
    sync(event);
}
for event, err := range client.Events.All(ctx, nil) {
    if err != nil {
        return err
    }
    sync(event)
}
client.events.list_all do |event|
  sync(event)
end
await foreach (var seatEvent in client.Events.ListAllAsync())
{
    await SyncAsync(seatEvent);
}

Walking the whole catalogue also drops the per-event availability counts, which cost one internal round-trip each — exactly the cost pagination exists to avoid.

Source

Language Package Repository
Node.js @seatlayer/server seatlayer/seatlayer-node
Python seatlayer seatlayer/seatlayer-python
PHP seatlayer/seatlayer-php seatlayer/seatlayer-php
Java io.seatlayer:seatlayer-java seatlayer/seatlayer-java
Go github.com/seatlayer/seatlayer-go seatlayer/seatlayer-go
Ruby seatlayer seatlayer/seatlayer-ruby
.NET SeatLayer seatlayer/seatlayer-dotnet

Both are MIT licensed. Working in another language? The API is plain HTTP and JSON — start from the server API reference and the OpenAPI description.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close