Developer Docs

DoorSplit API

Integrate event discovery, ticket sales, and door management into your website or app. All public endpoints work without authentication.

Base URL: https://api.doorsplit.appJSON responses

Getting Started

Authentication

Many endpoints are public and require no authentication (discovering events, validating tickets, fee estimates). For endpoints that manage data, send a Bearer token:

Authorization: Bearer <your-jwt-token>

Tokens are obtained via OIDC login through the DoorSplit app. All responses are JSON. Errors return { "error": "message" }.

Embed Widget

The easiest way to add DoorSplit events to your website. No API calls needed — just a script tag. The widget fetches events automatically and renders styled cards with ticket links.

Show all events for your organization:

<div id="doorsplit-events"></div>
<script src="https://api.doorsplit.app/widget/v1/embed.js"
  data-org-slug="YOUR_ORG_SLUG"
  async></script>

Show a single event:

<div id="doorsplit-events"></div>
<script src="https://api.doorsplit.app/widget/v1/embed.js"
  data-event-id="EVENT_ID"
  async></script>

Attributes

data-org-slugShow events by org slug (recommended)
data-org-idShow events by org ID
data-event-idShow a single event
data-layout"card" (default), "list", or "compact"
data-accent-colorCSS color for buttons and accents
data-max-eventsMaximum events to display (default 50)
data-checkout"popup" (default) or "redirect"
data-theme"light" (default) or "dark"
data-containerID of the target DOM element

Discover Events

Search and list published events. Great for building custom event listings, calendars, or aggregator sites. No authentication required.

GET/discover/eventsPublic

Returns all published events. Filter by organization or date range.

Query parameters:

org_id — filter by organization UUID
date_from — ISO 8601 start date
date_to — ISO 8601 end date
curl "https://api.doorsplit.app/discover/events?org_id=abc-123&date_from=2026-06-01T00:00:00Z"
GET/orgs/{slug}Public

Look up an organization by its URL-friendly slug (e.g. the-midnight-club).

GET/orgs/{slug}/eventsPublic

List all published events for an organization by slug. Useful for building a venue or band's upcoming shows page.

GET/events/{event_id}Public

Get full details for a single event — name, venue, date/time, tiered pricing, poster URL. Authenticated callers with event access also receive private fields like capacity and tickets sold.

Event Details

Public event responses include these fields (private fields like capacity and tickets_sold are only included for authenticated organizers):

{
  "event_id": "uuid",
  "org_id": "uuid",
  "name": "Friday Night Rock",
  "description": "Live rock music...",
  "venue": "The Midnight Club",
  "venue_address": "123 Main St",
  "event_date": "2026-05-15T20:00:00Z",
  "door_time": "19:00",
  "start_time": "20:00",
  "end_time": "23:00",
  "price": "25.00",
  "tier1_price": "15.00",
  "tier1_end_date": "2026-04-15T23:59:59Z",
  "tier2_price": "20.00",
  "tier2_end_date": "2026-05-10T23:59:59Z",
  "currency": "USD",
  "poster_url": "https://..."
}

Checkout & Fees

Create Stripe Checkout sessions for ticket purchases. The API handles pricing tiers, service fees, and ticket creation automatically. For donation campaigns these same two endpoints take donation_amount in place of quantity — see Donation Campaigns.

Payments run only on Stripe's hosted checkout.

POST /checkout/sessions returns a checkout_urland the buyer pays on Stripe — card details never touch the DoorSplit API, and there is no endpoint that charges a card or payment method programmatically. To limit card-testing fraud, paid events also require the owner organization's Stripe Connect account to be verified, new organizations are capped until they build a processing history, and abusive traffic is rate-limited and monitored.

POST/checkout/fee-estimatePublic

Preview the fee breakdown before creating a checkout session. Shows face value, service fee, and buyer total. Use this to display transparent pricing to buyers.

curl -X POST https://api.doorsplit.app/checkout/fee-estimate \
  -H "Content-Type: application/json" \
  -d '{"event_id": "EVENT_ID", "quantity": 2}'

// Response:
{
  "face_value_cents": 2000,
  "service_fee_cents": 235,
  "buyer_total_cents": 2235,
  "platform_fee_cents": 140,
  "stripe_fee_cents": 95
}
POST/checkout/sessionsPublic or Bearer

Create a Stripe Checkout Session. Returns a checkout_url — redirect the buyer there to complete payment. Guest checkout requires customer_email; authenticated users are linked automatically.

curl -X POST https://api.doorsplit.app/checkout/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "EVENT_ID",
    "quantity": 2,
    "customer_email": "buyer@example.com",
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/events/EVENT_ID"
  }'
GET/checkout/sessions/{session_id}Public

Check payment status after Stripe redirects back. Returns payment_status and ticket IDs when paid.

Tickets

Retrieve and manage tickets. Users can view their own tickets; organizers can list tickets for their events.

GET/ticketsBearer

List the authenticated user's tickets. Returns ticket ID, event, status (valid/used/cancelled), QR code info, and purchase details.

GET/tickets/{ticket_id}Bearer

Get a single ticket with its pre-signed QR code URL. Users can view their own tickets.

GET/tickets/{ticket_id}/validatePublic

Lightweight public check — returns whether a ticket is valid without marking it as used. Useful for external validation systems.

POST/tickets/{ticket_id}/cancelBearer

Cancel a ticket. Users can cancel their own tickets.

Scanning at the Door

Scan tickets and admit groups at the event entrance. These endpoints require event collaborator or org admin access.

POST/tickets/{ticket_id}/scanBearer

Mark a single ticket as used. Returns 409 if already scanned, 410 if cancelled. Requires event collaborator or org admin access.

GET/ticket-groups/{group_id}Public

Get a ticket group summary — total tickets, how many are valid/used/cancelled. When someone bought multiple tickets, they share a single QR code (group). The door person scans it and sees how many to admit.

POST/ticket-groups/{group_id}/scanBearer

Admit N people from a group. For example: 3 tickets purchased, only 2 people present — admit 2 and the QR stays valid for 1 remaining ticket.

curl -X POST https://api.doorsplit.app/ticket-groups/GROUP_ID/scan \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"count": 2}'

// Response:
{
  "admitted": 2,
  "remaining": 1,
  "total": 3,
  "message": "Admitted 2, 1 remaining"
}
POST/events/{event_id}/door-paymentBearer

Create a payment session for walk-up purchases. Returns a QR code (checkout mode) or a PaymentIntent client_secret (tap-to-pay mode) for the buyer to pay at the door.

Event Management

Create, update, and manage events. Requires org admin or event collaborator access.

POST/eventsBearer

Create a new event with name, venue, date, pricing tiers, and capacity. Requires org admin of the event's organization.

POST/events/ai-draftBearer

Turn a plain-language event description into structured, ready-to-review fields. Returns { draft, assumptions, missing } and does not create anything — the organizer reviews the draft, then submits it via POST /events. Prices are USD dollars, event_date is a calendar date, and door/show times are 24-hour HH:MM.

curl -X POST https://api.doorsplit.app/events/ai-draft \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Music show at the Crystal Ballroom in Portland, OR. Show at 9, doors at 8. Early bird $5, regular $8.",
    "today": "2026-08-20"
  }'
PUT/events/{event_id}Bearer

Update event details, pricing, or status. Setting status to completed triggers automatic revenue payouts to all collaborator organizations via Stripe.

POST/events/{event_id}/collaboratorsBearer

Add a user or organization as an event collaborator. Set their role (admin, manager, door, or collaborator for revenue share with no access) and revenue share percentage.

POST/events/{event_id}/guest-ticketsBearer

Issue a free guest ticket by email. If the email belongs to an existing user, the ticket is linked to their account.

Donation Campaigns

An event with event_type: "donation" is a fundraising campaign rather than a show. The donor picks the amount, no ticket is issued, and because a campaign has no natural end its payouts repeat instead of settling once. Ticketed events are unchanged — a missing event_type means "ticketed".

What a campaign does not have

venue, door_time, capacity, price, and the tier fields are absent or meaningless. Reject them on create; ignore them on read.

Do not display event_date on a campaign. It is populated with the creation timestamp purely to satisfy a database index and is not a date the campaign cares about. Branch on event_type and hide the whole date/venue/price block.

POST/eventsBearer

Create a campaign by setting event_type. Only name and org_id are required beyond that — donation_config falls back to sensible defaults.

curl -X POST https://api.doorsplit.app/events \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "org_id": "ORG_ID",
    "name": "Rebuild the Rehearsal Room",
    "event_type": "donation",
    "donation_config": {
      "suggested_amounts": ["10.00", "25.00", "100.00"],
      "min_amount": "5.00",
      "allow_custom_amount": true,
      "goal_amount": "8000.00",
      "thank_you_message": "You keep the doors open. Thank you.",
      "allow_donor_message": true,
      "absorb_fees": false
    },
    "payout_mode": "scheduled",
    "payout_schedule": "monthly"
  }'

donation_config

suggested_amountsUp to 6 quick-pick amounts, as dollar strings.
min_amountSmallest accepted gift. Never below "1.00".
allow_custom_amountWhen false, donors must choose a suggested amount.
goal_amountDrives the progress meter. Presentational — exceeding it does not close the campaign.
absorb_feestrue: donor is charged exactly their amount and fees come out of proceeds. false (default): fees are added on top.

Public reads expose donations_count, donations_raised_cents, and donation_config so a campaign page can render progress. Payout settings are private to the organizer.

POST/checkout/sessionsPublic or Bearer

Same endpoint as tickets, but send donation_amount (a dollar string) or donation_amount_cents instead of quantity. Works on /checkout/fee-estimate too. No ticket is created — the donor receives a thank-you email.

curl -X POST https://api.doorsplit.app/checkout/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "CAMPAIGN_ID",
    "donation_amount": "25.00",
    "customer_email": "donor@example.com",
    "donor_name": "Jane Doe",
    "donor_message": "Keep it up!",
    "anonymous": false,
    "success_url": "https://yoursite.com/thanks?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/campaign"
  }'
GET/events/qr/{qr_token}Public

Resolves a scanned QR code to the public event. Campaigns use the same static QR codes as doors, so branch on event_type before you render — a campaign has no price, and pricing a quantity against a missing price is how scanners end up charging NaN. Read donation_config, prompt for an amount, and post donation_amount_cents.

qr_token has no effect on the donation path: fee absorption comes from donation_config.absorb_fees, not absorb_door_fees. You may also omit customer_email and let Stripe Checkout collect it — the receipt still sends.

GET/events/{event_id}/donationsBearer

List donations with a summary of what has been paid out and what is still awaiting release. Donations marked anonymous withhold the donor's name and email.

POST/events/{event_id}/payoutsBearer

Release everything received since the last cycle. Returns released: false with reason: "nothing_to_release"when there's nothing new, so it is safe to retry.

// Response after a successful cycle:
{
  "event_id": "CAMPAIGN_ID",
  "released": true,
  "payout_cycle_id": "pc_...",
  "donation_count": 34,
  "distributable_cents": 82150,
  "transfer_count": 3
}

Payout modes & cycle safety

manualNothing moves until the organizer triggers a payout.
scheduledRuns automatically at a weekly, biweekly, or monthly cadence.
on_completionFires when the campaign is completed, including when an optional donation_end_date passes.

Every cycle stamps the donations it is settling with a payout_cycle_id before attempting any transfer. A retried, duplicated, or partially failed run can therefore never pay the same donation twice, and a manual release is always available regardless of mode.

The collaborator role

Alongside admin, manager, and door, event collaborators accept a collaborator role: a revenue share with no operational access at all. It is excluded from event access entirely, not just from management, so the holder is paid without being able to read or modify the event. Useful on campaigns for co-beneficiaries and partner organizations.

Organizations

Manage organizations (bands, venues, promoters) and Stripe Connect payouts.

POST/organizationsBearer

Create an organization. The creator becomes org admin automatically.

GET/organizationsBearer

List organizations the authenticated user belongs to.

POST/organizations/{org_id}/stripe-connectBearer

Start Stripe Connect onboarding. Returns an onboarding URL to redirect the org admin to Stripe's hosted setup page.

POST/organizations/{org_id}/payouts/instantBearer

Request an instant payout for an event's ticket revenue. A 3% convenience fee is deducted. Standard payouts (on event completion) are free.

B2B / Reseller partners

The B2B layer lets an organization sell through partner organizations (touring promoters, group-sales agencies, subscription desks, resellers). Partners are just organizations — any org can be given a wholesale priceon another org's event and sell on its own DoorSplit storefront link, keeping the margin. Partners can never create or edit events, tiers, or base pricing — they only act on allocations.

Authentication

Owner-org and partner-org actions both accept either an org-admin Bearer JWT or an org API key:

X-Api-Key: dsk_org_<opaque>

Mint keys at POST /organizations/{org_id}/api-keys; the plaintext is returned once. On /partner/* endpoints, a user JWT that admins multiple orgs must pass ?org_id= to disambiguate.

Wholesale & on-platform settlement

The organizer sets a wholesale_price — the amount it keeps per ticket. The partner sells on its own DoorSplit storefront link (/l/{slug}) at any price at or above wholesale and keeps the difference. Every sale runs through DoorSplit/Stripe.

At payout the partner is paid attributed revenue − wholesale and the organizer keeps the wholesale, via the collaborator transfer pipeline (share_type: wholesale). A partner must connect a Stripe payout account before its link sells. Public availability = capacity − tickets_sold − reseller_holds; unsold seats auto-release at each allocation's auto_release_at cutoff.

Commission (a % of your own tracked sales) and revenue share (a % of event proceeds) are collaborator features; a promo code (organizer-managed) can credit a commission collaborator.

Org API keys

POST/organizations/{org_id}/api-keysBearer

Mint a dsk_org_… key. Returns the one-time api_key. Optional label.

curl -X POST https://api.doorsplit.app/organizations/ORG_ID/api-keys \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label": "Ticketing integration"}'

// Response (api_key shown once):
{ "key_id": "uuid", "api_key": "dsk_org_…", "api_key_shown_once": true, … }
GET/organizations/{org_id}/api-keysBearer

List keys (prefix only, no plaintext).

PUT/organizations/{org_id}/api-keys/{key_id}Bearer

Update label or set status: revoked (invalidates immediately).

POST/organizations/{org_id}/api-keys/{key_id}/rotateBearer

Rotate the plaintext (old key stops working). New api_key shown once.

Wholesale partners (owner org)

POST/events/{event_id}/resellersBearer / Key

Add a wholesale partner: quantity tickets on a tier (tier1, tier2, full) at a wholesale_price(what you keep per ticket). Auto-creates the partner's storefront link (storefront_slug). Rejects with 409 when public capacity is insufficient.

curl -X POST https://api.doorsplit.app/events/EVENT_ID/resellers \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reseller_org_id": "uuid",
    "tier": "full",
    "quantity": 30,
    "wholesale_price": "40.00",
    "auto_release_hours_before": 72
  }'
GET/events/{event_id}/resellersBearer / Key

List all allocations on an event.

PUT/events/{event_id}/resellers/{allocation_id}Bearer / Key

Adjust quantity (never below sold), auto_release_at, or notes.

POST/events/{event_id}/resellers/{allocation_id}/releaseBearer / Key

Release unsold seats back to public (idempotent). Also happens automatically at the cutoff.

POST/events/{event_id}/resellers/{allocation_id}/cancelBearer / Key

Remove a partner. Returns 409once they've sold tickets (release instead).

Partner endpoints

Called by the partner org with its own key or an org-admin JWT (?org_id= when multi-org).

GET/partner/allocationsKey / Bearer

All allocations held by the calling org.

GET/partner/tracked-linksKey / Bearer

The partner's storefront link(s). Set the selling price by updating custom_price_cents (clamped to ≥ the wholesale price) with a PUT.

curl -X PUT https://api.doorsplit.app/partner/tracked-links/LINK_ID \
  -H "X-Api-Key: dsk_org_…" \
  -H "Content-Type: application/json" \
  -d '{ "custom_price_cents": 6000 }'

Promo codes

POST/partner/promo-codesBearer / Key

Organizer-managed. Create a code scoped to one event; discount_type is percent, fixed (cents), or hidden_tier. Optionally set credit_collaborator_id to credit a commission collaborator on redemption.

GET/events/{event_id}/promo-codesBearer

List every code on an event (owner-org view).

POST/checkout/promo-codes/validatePublic

Preview a code's discount without redeeming. Buyers apply codes at checkout; the checkout flow redeems them atomically.

curl -X POST https://api.doorsplit.app/checkout/promo-codes/validate \
  -H "Content-Type: application/json" \
  -d '{"event_id": "EVENT_ID", "code": "TOURING10", "quantity": 2}'

// Response:
{ "code": "TOURING10", "discount_type": "percent", "discount_percent": 10 }
POST/checkout/promo-codes/redeemPublic

Atomically reserve quantity redemptions — for B2B integrations that hold a redemption before checkout.

Tracked links

POST/partner/tracked-linksKey / Bearer

Create a short public URL (https://doorsplit.app/l/{slug}) with per-visitor pricing overrides and/or a hidden-tier unlock. Provide custom_price OR discount_percent (mutually exclusive), or neither. Every link carries an auto-minted ref_code for attribution.

GET/events/{event_id}/tracked-linksBearer

List every link on an event (owner-org view).

GET/l/{slug}Public

Resolve a slug to the event's public data plus the link's pricing / hidden tier and a checkout_hint.ref. The frontend forwards that ref into POST /checkout/sessions so revenue attribution reuses the collaborator flow.

Checkout integration

POST /checkout/sessions accepts an optional ref (tracked-link / collaborator attribution) and promo_code. Attributed tickets are tagged sold_by_collaborator_id; promo codes are redeemed atomically as the session completes.

Full API Reference

Complete list of all non-admin API endpoints. Admin-only endpoints (requiring an API key) are omitted.

MethodEndpointDescription
GET/discover/eventsList published events with filters
GET/orgs/{slug}Get org by slug
GET/orgs/{slug}/eventsPublished events for an org
GET/eventsList events (public or full for organizers)
GET/events/{event_id}Get event details
POST/eventsCreate an event
PUT/events/{event_id}Update event (status triggers payouts)
DELETE/events/{event_id}Delete an event
GET/events/{event_id}/collaboratorsList collaborators
POST/events/{event_id}/collaboratorsAdd collaborator
PUT/events/{event_id}/collaborators/{id}Update collaborator role
DELETE/events/{event_id}/collaborators/{id}Remove collaborator
POST/events/{event_id}/guest-ticketsIssue guest ticket
GET/events/{event_id}/donationsList donations for a campaign
POST/events/{event_id}/payoutsRelease donations received since the last cycle
POST/events/{event_id}/posterUpload poster (presigned URL)
GET/events/{event_id}/posterGet poster URL
DELETE/events/{event_id}/posterDelete poster
POST/checkout/fee-estimatePreview fee breakdown
POST/checkout/sessionsCreate checkout session
GET/checkout/sessions/{id}Get checkout status
POST/events/{event_id}/door-paymentCreate door payment (QR/tap)
GET/events/{id}/door-payment/{sid}/statusPoll door payment status
POST/terminal/connection-tokensStripe Terminal token
GET/ticketsList your tickets
GET/tickets/{ticket_id}Get ticket + QR code
GET/tickets/{ticket_id}/validateValidate ticket
POST/tickets/{ticket_id}/scanScan ticket at door
POST/tickets/{ticket_id}/cancelCancel ticket
GET/ticket-groups/{group_id}Get group summary
POST/ticket-groups/{group_id}/scanAdmit from group
GET/users/meGet your profile
PUT/users/meUpdate your profile
GET/users/me/identitiesList linked login methods
POST/organizationsCreate organization
GET/organizationsList your organizations
GET/organizations/{org_id}Get organization
PUT/organizations/{org_id}Update organization
POST/organizations/{org_id}/usersAdd user to org
GET/organizations/{org_id}/usersList org members
DELETE/organizations/{org_id}/users/{uid}Remove user from org
POST/organizations/{org_id}/stripe-connectStart Stripe onboarding
GET/organizations/{org_id}/stripe-connectStripe Connect status
POST/organizations/{org_id}/payouts/instantRequest instant payout
POST/organizations/{org_id}/logoUpload logo (presigned URL)
GET/organizations/{org_id}/logoGet logo URL
DELETE/organizations/{org_id}/logoDelete logo
POST/organizations/{org_id}/imageUpload hero image
GET/organizations/{org_id}/imageGet hero image URL
DELETE/organizations/{org_id}/imageDelete hero image
GET/search/organizations?q=...Search orgs by name
GET/search/users?q=...Search users by email/name
POST/organizations/{org_id}/api-keysMint an org API key (one-time)
GET/organizations/{org_id}/api-keysList org API keys
PUT/organizations/{org_id}/api-keys/{id}Update / revoke a key
POST/organizations/{org_id}/api-keys/{id}/rotateRotate a key
POST/events/{event_id}/resellersGrant a reseller allocation
GET/events/{event_id}/resellersList event allocations
PUT/events/{event_id}/resellers/{id}Adjust allocation
POST/events/{event_id}/resellers/{id}/releaseRelease unsold seats
POST/events/{event_id}/resellers/{id}/cancelRemove a partner
GET/partner/allocationsList the org's allocations
GET/partner/tracked-linksStorefront link(s)
PUT/partner/tracked-links/{id}Set storefront price
POST/partner/promo-codesCreate promo code (organizer)
GET/events/{event_id}/promo-codesList event promo codes
POST/checkout/promo-codes/validatePreview a promo code
POST/checkout/promo-codes/redeemReserve promo redemptions
POST/partner/tracked-linksCreate tracked link
GET/events/{event_id}/tracked-linksList event tracked links
GET/l/{slug}Resolve tracked link for checkout

* Public endpoints return limited fields. Authenticated organizers receive full event data including capacity, tickets_sold, and collaborators. Key = org API key (X-Api-Key: dsk_org_…); see the B2B / Reseller partners section above.