Website integration
This page is the recommended recipe for connecting a crew's website to Sway: the site keeps its own design and hosting, and Sway becomes its source for the roster, events, tickets, forms and ambassador programme.
1. Call the API from your server
Keep the secret key on your server (API routes, server-side rendering, edge functions), never in a page.
- Store it as a secret (
SWAY_API_KEY), never in the repository or the client bundle. - Give your pages your own routes (
/api/site-data) that call Sway and return only what the page shows. - Check the key and its limits at start-up with
GET /v1/me. - Limits counted per visitor (booking requests, newsletter sign-ups, checkout, the ambassador routes) count the visitor your server names in the body as
client_ip, and your key as a whole with twenty times the room. Send it: without it, only the whole key is counted, so one visitor who keeps trying can use up the room of your whole site. - A fully static site with no server can use a publishable key (
sway_pk_) from the browser instead, for public reads only, listing the site's addresses on the key. See Two kinds of key.
2. Let caching carry the traffic
Sway caches every read per crew and serves repeats for free, so your site's traffic does not become API traffic. Do your part:
- Send
If-None-Match. Keep theETagof each answer; an unchanged resource answers304, with no body and no cost. - Keep your own copy for as long as Sway does, and serve it when the API is slow or unreachable:
| Data | Endpoints | Sway keeps it |
|---|---|---|
| Crew, roster, promoters, venues | /v1/crew, /v1/artists, /v1/promoters, /v1/venues | 5 minutes |
| Events, line-ups, days, timetables | /v1/events, /v1/events/{id}, /days, /timetable | 1 minute |
| Ticket tiers, availability | /v1/events/{id}/ticket-tiers, /availability | 30 seconds |
| Genres | /v1/genres | 1 hour |
- Ask for what you show.
?fields=trims answers,?expand=venue,lineup,ticket_tierson a promoter's feed replaces three requests per event,?ids=fetches 50 known events at once, andupdated_sincesyncs only what changed. - Listen instead of polling. With webhooks, refresh an event when
event.updatedarrives and add it onevent.published, instead of re-reading every page on a timer. Send those refreshes withCache-Control: no-cache, so that Sway reads them fresh rather than from a copy made before the change.
A site built this way makes a few API requests a minute whatever its traffic, and keeps working through a short outage.
3. Sell tickets
Payment always happens on Sway's checkout page. Two ways to get there:
A link. Each ticket tier carries a checkout_url. Render your own "Get tickets" button pointing at it. Nothing else to build.
A checkout opened by your site. With write:checkout, your server calls POST /v1/checkout-sessions with the tiers and quantities the visitor chose on your page, and redirects to the url it returns. Before that:
- check a coupon or an access code with
POST /v1/coupons/validateorPOST /v1/access-codes/validate, to show the discount or unlock hidden tiers before the visitor pays; - keep the
checkout_idfrom the answer to follow the checkout (GET /v1/checkout-sessions/{id}) or end it (POST /v1/checkout-sessions/{id}/expire) when the visitor leaves.
Then order.paid arrives on your webhook with "via_your_site": true.
Show availability from the tier's status (on_sale, scheduled, sold_out, off_sale) and GET /v1/events/{id}/availability. The API never exposes stock counts or buyer data.
4. Collect from your forms
| Your form | Endpoint | Scope |
|---|---|---|
| Booking enquiry | POST /v1/booking-requests | write:bookings |
| Newsletter sign-up | POST /v1/newsletter-signup | write:newsletter |
| Newsletter unsubscribe | POST /v1/newsletter-unsubscribe | write:newsletter |
| A Sway form rendered by your site | POST /v1/forms/{slug}/submit | write:forms |
Validate on your server first, and keep your own bot protection (a captcha, a honeypot) in front of each form: Sway sees your server, not the visitor. Pass the visitor's address where the endpoint asks for it (client_ip), so that Sway's own limits apply per visitor rather than to your whole site. Booking requests are limited to 10 a minute per visitor and 300 a day per crew.
Booking requests land in Crew admin → API → Booking requests; newsletter sign-ups and form answers in the promoter's CRM and the form's answers. Send Idempotency-Key on every one of these calls, so that a retry never records a submission twice.
5. Keep placeholders as a fallback
Keep your site's static content (sample artists, sample events) as a fallback layer rather than deleting it:
- If the API is unreachable and your cache is empty, render the placeholders instead of an empty page.
- If the API returns fewer items than the design needs, pad the grid.
- Merge order: API data, then your site's own selection, then placeholders.
The site stays presentable at all times, including before the first key is configured.
6. Run the ambassador portal on your server
When the promoter turns on sign-up from your website, the join page, the sign-in page and the member's own page are yours to render, and the programme's leaderboard, rewards and quests are yours to show (read:ambassadors). One rule: the browser holds a cookie of yours, never a Sway credential.
- Keep the member session (
amb_st_...) on your server, in a sealed first-party cookie or a session store. Sway refuses that header from a browser anyway. - Run your own bot protection on the join and sign-in forms. Sway renders neither.
- Trade the
?amb_token=...on your landing page for a session on your server, on first load, and drop it from the URL. - Never put a
discount_idin a page. Capture?amb=CODE, keep the code for the visit, and resolve it on your server when you open the checkout. - The member list, ledgers and statistics (
read:ambassador-members) are personal data for the promoter's own tools, never for a public page.
Checklist
-
SWAY_API_KEYstored as a server-side secret (or a publishable key limited to your websites) -
GET /v1/meused as a start-up check -
If-None-Matchsent, answers kept, stale copy served on errors -
fields,expand,idsandupdated_sinceused where they save requests - A webhook for
event.publishedandevent.updatedinstead of polling - Ticket buttons use
checkout_url, or your server opensPOST /v1/checkout-sessions - Forms posted from your server, with your own bot protection and an
Idempotency-Key - Placeholder content kept as a fallback
- Ambassador sessions kept on your server,
?amb=CODEresolved at checkout