Operator & team
Endpoints for your operator account and the people who work in it: read and
update the operator record, manage your billing and correspondence contacts,
define roles with a permission grid, and create portal users. These are
client-role, operator-scoped endpoints; each is
gated by a specific permission (for example operators.settings:write,
users.roles:write, users.table:write).
Endpoints
Section titled “Endpoints”| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | /api/my-operator |
Read the operator record | operator |
| PUT | /api/my-operator |
Update the operator record | operator |
| GET | /api/client/dashboard |
Portal bootstrap aggregate | operator |
| GET | /api/me/permissions |
Your effective permissions + catalogue | session |
| GET | /api/my-operator/members |
List billing & correspondence contacts | operator |
| GET | /api/my-operator/users-search |
Search users to link to a contact | operator |
| POST | /api/my-operator/members |
Add a contact | operator |
| PUT | /api/my-operator/members/:id |
Update a contact | operator |
| DELETE | /api/my-operator/members/:id |
Remove a contact | operator |
| GET | /api/my-roles |
List roles | operator |
| GET | /api/my-roles/:id |
Read a role + its permission grid | operator |
| POST | /api/my-roles |
Create a role | operator |
| PUT | /api/my-roles/:id |
Update a role, permissions and venues | operator |
| DELETE | /api/my-roles/:id |
Delete a role | operator |
| GET | /api/my-users |
List portal users | operator |
| POST | /api/my-users |
Create a portal user | operator |
| PUT | /api/my-users/:id |
Update a user | operator |
| DELETE | /api/my-users/:id |
Delete a user | operator |
| POST | /api/my-users/:id/resend-invite |
Re-send a pending user invite | operator |
The operator record
Section titled “The operator record”GET /api/my-operator reads your company record; PUT updates it (name is
required).
curl -X PUT "https://thesidedoor.co/api/my-operator" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Acme Hospitality","company_name":"Acme Ltd","company_reg":"12345678","vat_number":"GB123456789"}'const res = await fetch("https://thesidedoor.co/api/my-operator", { method: "PUT", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Acme Hospitality", company_name: "Acme Ltd", company_reg: "12345678", vat_number: "GB123456789", }),});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-operator");curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Acme Hospitality", "company_name" => "Acme Ltd", "company_reg" => "12345678", "vat_number" => "GB123456789", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true }Dashboard bootstrap
Section titled “Dashboard bootstrap”GET /api/client/dashboard returns the aggregate the client portal loads on
sign-in: the operator, your permissions, payout status, headline stats and your
venues.
curl "https://thesidedoor.co/api/client/dashboard" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/client/dashboard", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/client/dashboard");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "operator": { "id": "op_1", "name": "Acme Hospitality", "status": "active", "stripe_connect_status": "enabled" }, "payout_status": { "provider": "stripe", "status": "enabled", "source": "cached" }, "stats": { "venues": 3, "total_guests": 4200, "month_guests": 380, "revenue": 0 }, "staff_summary": { "count": 6 }, "venues": [{ "id": "ven_123", "name": "The Attic", "status": "live" }]}Your permissions
Section titled “Your permissions”GET /api/me/permissions returns your effective permission map plus the
permission catalogue (tree) used to build a role grid. It supports
If-None-Match/ETag for a 304.
curl "https://thesidedoor.co/api/me/permissions" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch("https://thesidedoor.co/api/me/permissions", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/me/permissions");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "role": "client", "portal": "client", "permissions": { "bookings.table": { "read": true, "write": false } }, "tree": [ { "key": "bookings", "label": "Guest list", "actions": [], "children": [ { "key": "bookings.table", "label": "Guest list", "actions": ["read"] }, { "key": "bookings.booking", "label": "Guest list detail", "actions": ["read", "write"] } ] }, { "key": "users", "label": "Access", "actions": [], "children": [ { "key": "users.table", "label": "Users", "actions": ["write"] }, { "key": "users.roles", "label": "Roles", "actions": ["write"] } ] } ]}Billing & correspondence contacts
Section titled “Billing & correspondence contacts”These /members endpoints manage your operator’s billing and correspondence
contacts, the people we email about invoices, account matters and general
correspondence. They are contact details only: they do not grant any access to
the platform. To add someone who actually signs into the portal, create a
portal user with POST /api/my-users instead.
Each contact has a type (one of billing, primary, technical or
general, defaulting to general), a name and a required, valid email. You
may pass an optional user_id to link an existing account user, which fills in
the name and email from that user if you leave them blank.
GET /api/my-operator/users-search?q= finds candidate users to link, then
POST /api/my-operator/members adds the contact.
curl -X POST "https://thesidedoor.co/api/my-operator/members" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"type":"billing","name":"Acme Accounts","email":"accounts@example.com","user_id":"usr_abc"}'const res = await fetch("https://thesidedoor.co/api/my-operator/members", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ type: "billing", name: "Acme Accounts", email: "accounts@example.com", user_id: "usr_abc", }),});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-operator/members");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "type" => "billing", "name" => "Acme Accounts", "email" => "accounts@example.com", "user_id" => "usr_abc", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "oc_9f2a..." }Update a contact’s type, name and email with
PUT /api/my-operator/members/:id, or remove it with
DELETE /api/my-operator/members/:id; both return { "success": true }.
A role is created with a name and description, then its permission grid and venue
scope are set with PUT. Create a role first:
curl -X POST "https://thesidedoor.co/api/my-roles" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Front of House","description":"FOH staff"}'const res = await fetch("https://thesidedoor.co/api/my-roles", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Front of House", description: "FOH staff" }),});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-roles");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Front of House", "description" => "FOH staff", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "id": "role_7f3" }Set the permission grid
Section titled “Set the permission grid”PUT /api/my-roles/:id replaces the permission grid and venue scope. Each grid
row is { permission_key, can_read, can_write } - pull the available keys from
the tree in /api/me/permissions. An empty or omitted
venue_ids means all account venues.
curl -X PUT "https://thesidedoor.co/api/my-roles/ROLE_ID" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Front of House", "permissions": [ { "permission_key": "bookings.table", "can_read": true, "can_write": false }, { "permission_key": "guests.table", "can_read": true, "can_write": false } ], "venue_ids": ["VENUE_ID"] }'const res = await fetch("https://thesidedoor.co/api/my-roles/ROLE_ID", { method: "PUT", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Front of House", permissions: [ { permission_key: "bookings.table", can_read: true, can_write: false }, { permission_key: "guests.table", can_read: true, can_write: false }, ], venue_ids: ["VENUE_ID"], }),});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-roles/ROLE_ID");curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Front of House", "permissions" => [ ["permission_key" => "bookings.table", "can_read" => true, "can_write" => false], ["permission_key" => "guests.table", "can_read" => true, "can_write" => false], ], "venue_ids" => ["VENUE_ID"], ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true }GET /api/my-roles/:id returns the stored grid as permissions and the scoped
venue_ids; DELETE /api/my-roles/:id removes a role. A role can’t be deleted
while it’s a system role or one of the account’s default roles.
Portal users sign in and are assigned exactly one role. POST /api/my-users
takes name, email, a role_id that belongs to your account, and an
optional phone - no password: the new user gets an emailed invite and
sets their own password when they accept it.
curl -X POST "https://thesidedoor.co/api/my-users" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Jane Doe","email":"jane@example.com","role_id":"ROLE_ID","phone":"+44..."}'const res = await fetch("https://thesidedoor.co/api/my-users", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Jane Doe", email: "jane@example.com", role_id: "ROLE_ID", phone: "+44...", }),});const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-users");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "name" => "Jane Doe", "email" => "jane@example.com", "role_id" => "ROLE_ID", "phone" => "+44...", ]),]);$data = json_decode(curl_exec($ch), true);{ "success": true, "invited": true }PUT /api/my-users/:id accepts any subset of name, email, password,
phone and role_id (a new password is only set when non-empty, and sets
it directly rather than sending an invite);
DELETE /api/my-users/:id removes a user (you cannot delete yourself). Both
return { "success": true }.
Resend an invite
Section titled “Resend an invite”POST /api/my-users/:id/resend-invite sends a fresh invite email to a user
who hasn’t yet accepted theirs - it mints a new link (superseding the old
one) and re-sends it. It only works while the user is still pending; a user
who has already accepted returns a 400.
curl -X POST "https://thesidedoor.co/api/my-users/USER_ID/resend-invite" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-users/USER_ID/resend-invite", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-users/USER_ID/resend-invite");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "success": true }