Bundles, promo codes & tags
The catalogue resources that dress up bookings: bundles of paid add-ons,
promo codes for discounts, and tags for classifying guests and
reservations. This page also covers copy-from, which clones a settings scope
from another of your venues. Every endpoint is under /api/my-venues/:venueId/…
and is operator (client role, venue-scoped) - send an Authorization: Bearer token from the Authentication flow. Prices
are integer pence.
| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET/POST | /api/my-venues/:venueId/bundles |
List/create bundles | operator |
| PUT/DELETE | /api/my-venues/:venueId/bundles/:id |
Update/delete a bundle | operator |
| POST | /api/my-venues/:venueId/bundles/:id/items |
Add a bundle item | operator |
| PUT/DELETE | /api/my-venues/:venueId/bundles/:id/items/:itemId |
Update/delete an item | operator |
| POST | /api/my-venues/:venueId/bundles/:id/items/:itemId/image |
Upload item image | operator |
| POST | /api/my-venues/:venueId/copy-from |
Copy a settings scope from another venue | operator |
| GET/POST | /api/my-venues/:venueId/promo-codes |
List/create promo codes | operator |
| PUT/DELETE | /api/my-venues/:venueId/promo-codes/:id |
Update/delete a promo code | operator |
| POST | /api/my-venues/:venueId/promo-codes/:id/reset |
Reset redemption count | operator |
| GET | /api/my/tag-catalogue |
Account-wide default tags | operator |
| GET | /api/my-venues/:venueId/tag-catalogue |
Venue tag catalogue | operator |
| POST/DELETE | /api/my-venues/:venueId/tags |
Add/remove a custom tag | operator |
| POST | /api/my-venues/:venueId/tag-categories |
Add a custom category | operator |
| PATCH | /api/my-venues/:venueId/tag-categories |
Update category visibility | operator |
Bundles
Section titled “Bundles”A bundle has a name and an optional applicable_profiles array ("tables",
"events"; omit to apply to both). Items live under it, each with a name,
price (pence), optional max_qty and image_url.
Create a bundle and add an item
Section titled “Create a bundle and add an item”curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/bundles" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"VIP Package","applicable_profiles":["tables"]}'const res = await fetch("https://thesidedoor.co/api/my-venues/VENUE_ID/bundles", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "VIP Package", applicable_profiles: ["tables"] }),});const { id } = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-venues/VENUE_ID/bundles");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "VIP Package", "applicable_profiles" => ["tables"], ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "bnd_vip" }Then POST /api/my-venues/:venueId/bundles/:id/items to add an item:
curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/bundles/bnd_vip/items" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Bottle of Champagne","price":12000,"max_qty":5}'const res = await fetch( "https://thesidedoor.co/api/my-venues/VENUE_ID/bundles/bnd_vip/items", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Bottle of Champagne", price: 12000, max_qty: 5, }), },);const data = await res.json();$ch = curl_init( "https://thesidedoor.co/api/my-venues/VENUE_ID/bundles/bnd_vip/items");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Bottle of Champagne", "price" => 12000, "max_qty" => 5, ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "itm_champ" }Update an item with PUT .../bundles/:id/items/:itemId and delete it with
DELETE .../bundles/:id/items/:itemId. To give an item a picture, POST .../bundles/:id/items/:itemId/image with a base64 data URL in a data field
(max 2 MB); the response returns { "success": true, "url": "…" } and stores it
as the item’s image_url.
Copy settings from another venue
Section titled “Copy settings from another venue”POST /api/my-venues/:venueId/copy-from clones a scope of settings from a source
venue you also own into this one. This overwrites the target scope. Provide
source_venue_id and a scope.
Allowed scope values: bundles, shifts, access-rules, availability,
tables, queue, payment-policies, access-profiles, admission-rules,
promo-codes, client-tags, reservation-tags.
curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/copy-from" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"source_venue_id":"SOURCE_VENUE_ID","scope":"bundles"}'const res = await fetch( "https://thesidedoor.co/api/my-venues/VENUE_ID/copy-from", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ source_venue_id: "SOURCE_VENUE_ID", scope: "bundles", }), },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-venues/VENUE_ID/copy-from");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "source_venue_id" => "SOURCE_VENUE_ID", "scope" => "bundles", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "copied": 4 }Promo codes
Section titled “Promo codes”A promo code has a name, a unique code (uppercased server-side), a
discount_pct (0–100) and optional max_uses and expires_at. A duplicate code
for the same venue returns 409.
Create a promo code
Section titled “Create a promo code”curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Summer Launch","code":"SUMMER25","discount_pct":25,"max_uses":100,"expires_at":"2026-09-01T00:00:00.000Z"}'const res = await fetch( "https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Summer Launch", code: "SUMMER25", discount_pct: 25, max_uses: 100, expires_at: "2026-09-01T00:00:00.000Z", }), },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Summer Launch", "code" => "SUMMER25", "discount_pct" => 25, "max_uses" => 100, "expires_at" => "2026-09-01T00:00:00.000Z", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "promo_summer" }Update with PUT .../promo-codes/:id and delete with DELETE .../promo-codes/:id. To zero the redemption counter without deleting the code:
curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes/promo_summer/reset" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"await fetch( "https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes/promo_summer/reset", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` }, },);$ch = curl_init( "https://thesidedoor.co/api/my-venues/VENUE_ID/promo-codes/promo_summer/reset");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);curl_exec($ch);{ "success": true }Tags come in two kinds: guest (CRM labels) and reservation (booking
labels). Platform defaults are read-only; you can add venue-custom tags and
categories on top. GET /api/my-venues/:venueId/tag-catalogue?kind=guest returns
the merged view (defaults + custom + category flags); GET /api/my/tag-catalogue
returns just the account-wide defaults.
Add a custom category, then a tag
Section titled “Add a custom category, then a tag”POST /api/my-venues/:venueId/tag-categories creates a category with kind and
name. Category visibility is tuned later with PATCH /api/my-venues/:venueId/tag-categories (allow_guest_select, show_on_slip,
show_in_summary).
curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/tag-categories" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"guest","name":"VIP"}'await fetch("https://thesidedoor.co/api/my-venues/VENUE_ID/tag-categories", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ kind: "guest", name: "VIP" }),});$ch = curl_init("https://thesidedoor.co/api/my-venues/VENUE_ID/tag-categories");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["kind" => "guest", "name" => "VIP"]),]);curl_exec($ch);{ "success": true }Then POST /api/my-venues/:venueId/tags adds a tag under it with kind,
category and label:
curl -X POST "https://thesidedoor.co/api/my-venues/VENUE_ID/tags" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"guest","category":"VIP","label":"Regular"}'await fetch("https://thesidedoor.co/api/my-venues/VENUE_ID/tags", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ kind: "guest", category: "VIP", label: "Regular" }),});$ch = curl_init("https://thesidedoor.co/api/my-venues/VENUE_ID/tags");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "kind" => "guest", "category" => "VIP", "label" => "Regular", ]),]);curl_exec($ch);{ "success": true }Remove a custom tag with DELETE /api/my-venues/:venueId/tags - pass kind,
category and label as query-string parameters (platform defaults cannot
be removed).