Developer Docs
DoorSplit API
Integrate event discovery, ticket sales, and door management into your website or app. All public endpoints work without authentication.
https://api.doorsplit.appJSON responsesGetting 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 IDdata-event-idShow a single eventdata-layout"card" (default), "list", or "compact"data-accent-colorCSS color for buttons and accentsdata-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 elementDiscover Events
Search and list published events. Great for building custom event listings, calendars, or aggregator sites. No authentication required.
/discover/eventsPublicReturns all published events. Filter by organization or date range.
Query parameters:
org_id — filter by organization UUIDdate_from — ISO 8601 start datedate_to — ISO 8601 end datecurl "https://api.doorsplit.app/discover/events?org_id=abc-123&date_from=2026-06-01T00:00:00Z"/orgs/{slug}PublicLook up an organization by its URL-friendly slug (e.g. the-midnight-club).
/orgs/{slug}/eventsPublicList all published events for an organization by slug. Useful for building a venue or band's upcoming shows page.
/events/{event_id}PublicGet 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.
/checkout/fee-estimatePublicPreview 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
}/checkout/sessionsPublic or BearerCreate 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"
}'/checkout/sessions/{session_id}PublicCheck 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.
/ticketsBearerList the authenticated user's tickets. Returns ticket ID, event, status (valid/used/cancelled), QR code info, and purchase details.
/tickets/{ticket_id}BearerGet a single ticket with its pre-signed QR code URL. Users can view their own tickets.
/tickets/{ticket_id}/validatePublicLightweight public check — returns whether a ticket is valid without marking it as used. Useful for external validation systems.
/tickets/{ticket_id}/cancelBearerCancel 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.
/tickets/{ticket_id}/scanBearerMark a single ticket as used. Returns 409 if already scanned, 410 if cancelled. Requires event collaborator or org admin access.
/ticket-groups/{group_id}PublicGet 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.
/ticket-groups/{group_id}/scanBearerAdmit 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"
}/events/{event_id}/door-paymentBearerCreate 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.
/eventsBearerCreate a new event with name, venue, date, pricing tiers, and capacity. Requires org admin of the event's organization.
/events/ai-draftBearerTurn 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"
}'/events/{event_id}BearerUpdate event details, pricing, or status. Setting status to completed triggers automatic revenue payouts to all collaborator organizations via Stripe.
/events/{event_id}/collaboratorsBearerAdd 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.
/events/{event_id}/guest-ticketsBearerIssue 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.
/eventsBearerCreate 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.
/checkout/sessionsPublic or BearerSame 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"
}'/events/qr/{qr_token}PublicResolves 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.
/events/{event_id}/donationsBearerList 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.
/events/{event_id}/payoutsBearerRelease 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.
/organizationsBearerCreate an organization. The creator becomes org admin automatically.
/organizationsBearerList organizations the authenticated user belongs to.
/organizations/{org_id}/stripe-connectBearerStart Stripe Connect onboarding. Returns an onboarding URL to redirect the org admin to Stripe's hosted setup page.
/organizations/{org_id}/payouts/instantBearerRequest 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
/organizations/{org_id}/api-keysBearerMint 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, … }/organizations/{org_id}/api-keysBearerList keys (prefix only, no plaintext).
/organizations/{org_id}/api-keys/{key_id}BearerUpdate label or set status: revoked (invalidates immediately).
/organizations/{org_id}/api-keys/{key_id}/rotateBearerRotate the plaintext (old key stops working). New api_key shown once.
Wholesale partners (owner org)
/events/{event_id}/resellersBearer / KeyAdd 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
}'/events/{event_id}/resellersBearer / KeyList all allocations on an event.
/events/{event_id}/resellers/{allocation_id}Bearer / KeyAdjust quantity (never below sold), auto_release_at, or notes.
/events/{event_id}/resellers/{allocation_id}/releaseBearer / KeyRelease unsold seats back to public (idempotent). Also happens automatically at the cutoff.
/events/{event_id}/resellers/{allocation_id}/cancelBearer / KeyRemove 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).
/partner/allocationsKey / BearerAll allocations held by the calling org.
/partner/tracked-linksKey / BearerThe 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
/partner/promo-codesBearer / KeyOrganizer-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.
/events/{event_id}/promo-codesBearerList every code on an event (owner-org view).
/checkout/promo-codes/validatePublicPreview 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 }/checkout/promo-codes/redeemPublicAtomically reserve quantity redemptions — for B2B integrations that hold a redemption before checkout.
Tracked links
/partner/tracked-linksKey / BearerCreate 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.
/events/{event_id}/tracked-linksBearerList every link on an event (owner-org view).
/l/{slug}PublicResolve 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.
| Method | Endpoint | Description |
|---|---|---|
| GET | /discover/events | List published events with filters |
| GET | /orgs/{slug} | Get org by slug |
| GET | /orgs/{slug}/events | Published events for an org |
| GET | /events | List events (public or full for organizers) |
| GET | /events/{event_id} | Get event details |
| POST | /events | Create an event |
| PUT | /events/{event_id} | Update event (status triggers payouts) |
| DELETE | /events/{event_id} | Delete an event |
| GET | /events/{event_id}/collaborators | List collaborators |
| POST | /events/{event_id}/collaborators | Add collaborator |
| PUT | /events/{event_id}/collaborators/{id} | Update collaborator role |
| DELETE | /events/{event_id}/collaborators/{id} | Remove collaborator |
| POST | /events/{event_id}/guest-tickets | Issue guest ticket |
| GET | /events/{event_id}/donations | List donations for a campaign |
| POST | /events/{event_id}/payouts | Release donations received since the last cycle |
| POST | /events/{event_id}/poster | Upload poster (presigned URL) |
| GET | /events/{event_id}/poster | Get poster URL |
| DELETE | /events/{event_id}/poster | Delete poster |
| POST | /checkout/fee-estimate | Preview fee breakdown |
| POST | /checkout/sessions | Create checkout session |
| GET | /checkout/sessions/{id} | Get checkout status |
| POST | /events/{event_id}/door-payment | Create door payment (QR/tap) |
| GET | /events/{id}/door-payment/{sid}/status | Poll door payment status |
| POST | /terminal/connection-tokens | Stripe Terminal token |
| GET | /tickets | List your tickets |
| GET | /tickets/{ticket_id} | Get ticket + QR code |
| GET | /tickets/{ticket_id}/validate | Validate ticket |
| POST | /tickets/{ticket_id}/scan | Scan ticket at door |
| POST | /tickets/{ticket_id}/cancel | Cancel ticket |
| GET | /ticket-groups/{group_id} | Get group summary |
| POST | /ticket-groups/{group_id}/scan | Admit from group |
| GET | /users/me | Get your profile |
| PUT | /users/me | Update your profile |
| GET | /users/me/identities | List linked login methods |
| POST | /organizations | Create organization |
| GET | /organizations | List your organizations |
| GET | /organizations/{org_id} | Get organization |
| PUT | /organizations/{org_id} | Update organization |
| POST | /organizations/{org_id}/users | Add user to org |
| GET | /organizations/{org_id}/users | List org members |
| DELETE | /organizations/{org_id}/users/{uid} | Remove user from org |
| POST | /organizations/{org_id}/stripe-connect | Start Stripe onboarding |
| GET | /organizations/{org_id}/stripe-connect | Stripe Connect status |
| POST | /organizations/{org_id}/payouts/instant | Request instant payout |
| POST | /organizations/{org_id}/logo | Upload logo (presigned URL) |
| GET | /organizations/{org_id}/logo | Get logo URL |
| DELETE | /organizations/{org_id}/logo | Delete logo |
| POST | /organizations/{org_id}/image | Upload hero image |
| GET | /organizations/{org_id}/image | Get hero image URL |
| DELETE | /organizations/{org_id}/image | Delete hero image |
| GET | /search/organizations?q=... | Search orgs by name |
| GET | /search/users?q=... | Search users by email/name |
| POST | /organizations/{org_id}/api-keys | Mint an org API key (one-time) |
| GET | /organizations/{org_id}/api-keys | List org API keys |
| PUT | /organizations/{org_id}/api-keys/{id} | Update / revoke a key |
| POST | /organizations/{org_id}/api-keys/{id}/rotate | Rotate a key |
| POST | /events/{event_id}/resellers | Grant a reseller allocation |
| GET | /events/{event_id}/resellers | List event allocations |
| PUT | /events/{event_id}/resellers/{id} | Adjust allocation |
| POST | /events/{event_id}/resellers/{id}/release | Release unsold seats |
| POST | /events/{event_id}/resellers/{id}/cancel | Remove a partner |
| GET | /partner/allocations | List the org's allocations |
| GET | /partner/tracked-links | Storefront link(s) |
| PUT | /partner/tracked-links/{id} | Set storefront price |
| POST | /partner/promo-codes | Create promo code (organizer) |
| GET | /events/{event_id}/promo-codes | List event promo codes |
| POST | /checkout/promo-codes/validate | Preview a promo code |
| POST | /checkout/promo-codes/redeem | Reserve promo redemptions |
| POST | /partner/tracked-links | Create tracked link |
| GET | /events/{event_id}/tracked-links | List 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.