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/image |
Upload bundle banner image | 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 |
| PUT | /api/my-venues/:venueId/bundles/:id/items/reorder |
Persist a drag-reordered item list | 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 |
| PATCH | /api/my-venues/:venueId/tags |
Set/clear a tag colour or rename 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, an optional applicable_profiles array ("tables",
"events"; omit to apply to both), and an optional image_url for the wide
banner shown above the bundle in the booking wizard and widget. To set it,
POST .../bundles/:id/image with a base64 data URL in a data field (max
2 MB, cropped to a 4:1 aspect); the response returns { "success": true, "url": "…" }. PUT .../bundles/:id with image_url: null removes it. Items live
under a bundle, each with a name, price (pence), optional max_qty and
image_url.
An item can also carry an optional variant_groups array of configurable option
sets (for example “Size” or “Add-ons”). Each group is
{ "id", "name", "type", "options" }, where type is "single" (pick one) or
"multi" (pick any number), and each option is
{ "id", "label", "price" } with price a non-negative pence modifier added on
top of the item price. You may omit each id on create and one is generated for
you; omit variant_groups (or send an empty array) for a plain item.
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.
Reorder items
Section titled “Reorder items”PUT /api/my-venues/:venueId/bundles/:id/items/reorder persists a drag-reordered
item list. Send { "item_ids": [...] } with every item id in the order you want;
each item’s sort order is set to its position in the array. item_ids is
required (an empty list returns 400).
curl -X PUT "https://thesidedoor.co/api/my-venues/VENUE_ID/bundles/bnd_vip/items/reorder" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"item_ids":["itm_champ","itm_cake","itm_flowers"]}'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, staffing, access-rules,
availability, tables, notifications, payment-policies, access-profiles,
admission-rules, promo-codes, client-tags, reservation-tags,
guestlist-tags, branding, widgets, vouchers. Any other value returns
400.
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 and a unique code (uppercased server-side). Its
discount is set by discount_type, which is either "percent" (the default) or
"amount":
- Percent (
discount_type: "percent"): setdiscount_pct(1–100). You may add an optionaldiscount_cap_pence“up to” cap so a percentage discount never exceeds that many pence. - Amount (
discount_type: "amount"): setdiscount_amount_pence(greater than 0), a flat discount in pence.
Optional max_uses caps total redemptions, and max_per_person_uses caps how
many times a single guest can use the code. expires_at is an optional ISO
timestamp. 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_type":"percent","discount_pct":25,"discount_cap_pence":2000,"max_uses":100,"max_per_person_uses":1,"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_type: "percent", discount_pct: 25, discount_cap_pence: 2000, max_uses: 100, max_per_person_uses: 1, 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_type" => "percent", "discount_pct" => 25, "discount_cap_pence" => 2000, "max_uses" => 100, "max_per_person_uses" => 1, "expires_at" => "2026-09-01T00:00:00.000Z", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "promo_summer" }For a flat discount instead, send "discount_type": "amount" with
"discount_amount_pence" (for example {"name":"Fiver off","code":"FIVER", "discount_type":"amount","discount_amount_pence":500}).
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 three kinds: guest (CRM labels), reservation (booking labels)
and guestlist (guest-list request 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, plus an optional colour (a #rgb or #rrggbb hex; an
invalid value returns 400):
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).
Recolour or rename a tag
Section titled “Recolour or rename a tag”PATCH /api/my-venues/:venueId/tags sets or clears a tag’s colour, and (for a
custom tag) renames its label. This is separate from PATCH /api/my-venues/:venueId/tag-categories, which tunes category visibility. Send
kind, category and label to identify the tag, then:
colour: a#rgbor#rrggbbhex to set it, or""/nullto clear it. A colour can be set on any tag, including a platform default. Omit the key to leave the colour unchanged.newLabel: a new label to rename a custom tag.
curl -X PATCH "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","colour":"#8b5cf6","newLabel":"Regulars"}'