Reports & analytics
Business analytics for your venues: a set of ready-made metric endpoints
(covers, demand, operations, guests, reviews, vouchers, events…), a
report builder you can query ad hoc or with a natural-language question, and
saved reports and dashboards to come back to. Every endpoint is operator
(client role) and scoped to venues you can access with venueId (omit it
to combine every venue you can access) - send an Authorization: Bearer
token from the Authentication flow. Some
fields (guest names and contacts, exact revenue) only appear if your role
has the matching permission grant - a role without it still gets the
aggregate numbers, just without the sensitive detail.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | /api/my-analytics/overview |
Home KPI overview | operator |
| GET | /api/my-analytics/heatmap |
Covers by day-of-week x hour | operator |
| GET | /api/my-analytics/calendar |
Covers/bookings per day for a month | operator |
| GET | /api/my-analytics/day |
Drill into one date’s bookings | operator |
| GET | /api/my-analytics/operations |
Table utilisation, turn time, no-shows | operator |
| GET | /api/my-analytics/guests |
Guest analytics (new vs returning, segments) | operator |
| GET | /api/my-analytics/demand |
Booking demand funnel | operator |
| GET | /api/my-analytics/reviews |
Guest review summary and list | operator |
| GET | /api/my-analytics/reviews/:id/booking |
The booking a review was for | operator |
| POST | /api/my-analytics/reviews/:id/flag |
Flag or unflag a review as fake | operator |
| GET | /api/my-analytics/vouchers |
Gift voucher performance | operator |
| GET | /api/my-analytics/events |
Per-event ticket performance | operator |
| GET | /api/my-analytics/revpash |
Revenue per available seat-hour | operator |
| GET | /api/my-analytics/revenue-reconciliation |
Key-paid bookings against ledger credit for a month | operator |
| GET | /api/my-analytics/brief |
Weekday trend summary | operator |
| GET | /api/my-analytics/export |
Bulk CSV/print export | operator |
| GET | /api/my-analytics/reports |
List standard one-click reports | operator |
| GET | /api/my-analytics/catalog |
Report builder’s field catalogue | operator |
| POST | /api/my-analytics/report |
Run an ad-hoc report | operator |
| POST | /api/my-analytics/nl-report |
Turn a question into a report | operator, Aimy add-on |
| GET | /api/my-analytics/saved |
List saved reports | operator |
| POST | /api/my-analytics/saved |
Save a report | operator |
| PUT | /api/my-analytics/saved/:id |
Update a saved report | operator |
| DELETE | /api/my-analytics/saved/:id |
Delete a saved report | operator |
| GET | /api/my-analytics/dashboards |
List dashboards | operator |
| POST | /api/my-analytics/dashboards |
Create a dashboard | operator |
| PUT | /api/my-analytics/dashboards/:id |
Update a dashboard | operator |
| DELETE | /api/my-analytics/dashboards/:id |
Delete a dashboard | operator |
| POST | /api/my-analytics/dashboards/:id/pin |
Pin/unpin a dashboard | operator |
| GET | /api/my-analytics/dashboards/:id/render |
Render a dashboard’s panels | operator |
| GET | /api/my-analytics/prebuilt-dashboards |
List Sidedoor’s curated dashboards | operator |
| GET | /api/my-analytics/prebuilt-dashboards/:id/render |
Render a curated dashboard | operator |
Overview
Section titled “Overview”GET /api/my-analytics/overview is the analytics home screen: headline
KPIs (bookings, covers, unique guests, no-show rate, average rating, review
count), a trend series, covers by hour, and a per-venue breakdown. Takes
days (0-730, default 90) and venueId.
curl "https://thesidedoor.co/api/my-analytics/overview?days=90" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/overview?days=90", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/overview?days=90");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "period": "2026-04-19/2026-07-17", "days": 90, "kpis": { "bookings": 640, "covers": 1920, "uniqueGuests": 1040, "noShowRate": 4.2, "avgRating": 4.6, "reviewCount": 212 }, "trend": [{ "date": "2026-07-17", "covers": 86, "bookings": 28 }], "hourly": [{ "hour": 19, "covers": 320 }], "byVenue": [{ "id": "ven_123", "name": "The Attic", "covers": 1920 }]}Covers heatmap and calendar
Section titled “Covers heatmap and calendar”GET /api/my-analytics/heatmap buckets covers by day-of-week and hour, in
each venue’s own local time, with days and venueId. GET /api/my-analytics/calendar returns covers and bookings per day for one
month (YYYY-MM).
curl "https://thesidedoor.co/api/my-analytics/heatmap?days=90" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-analytics/calendar?month=2026-07" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const heatmap = await fetch( "https://thesidedoor.co/api/my-analytics/heatmap?days=90", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());
const calendar = await fetch( "https://thesidedoor.co/api/my-analytics/calendar?month=2026-07", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/heatmap?days=90");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$heatmap = json_decode(curl_exec($ch), true);{ "period": "...", "days": 90, "minHour": 11, "maxHour": 23, "peak": { "dow": 5, "hour": 20, "covers": 140 }, "grid": [[0, 0, 4]] }{ "month": "2026-07", "days": { "2026-07-17": { "covers": 86, "bookings": 28 } }, "totalCovers": 1920, "totalBookings": 640, "maxCovers": 140}grid is a 7 x 24 matrix of covers, one row per day of the week.
One date’s bookings
Section titled “One date’s bookings”GET /api/my-analytics/day drills into a single date’s bookings and
reservations, in order. Takes date (required, YYYY-MM-DD) and
venueId. The guest field on each row only appears with the guest-PII
permission grant.
curl "https://thesidedoor.co/api/my-analytics/day?date=2026-07-17" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/day?date=2026-07-17", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/day?date=2026-07-17");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "date": "2026-07-17", "items": [{ "time": "19:30", "covers": 4, "status": "seated", "venue": "The Attic" }], "totalCovers": 86, "bookings": 28, "pii": false}Operations
Section titled “Operations”GET /api/my-analytics/operations covers table-service KPIs: table
utilisation, average turn time, overstay, and the economics of no-shows
(lost covers, and where relevant lost minimum spend or deposits
recovered). Takes days (0-365) and venueId.
curl "https://thesidedoor.co/api/my-analytics/operations?days=30" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/operations?days=30", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/operations?days=30");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "period": "...", "days": 30, "utilisation": { "pct": 68 }, "turnTime": { "avgMinutes": 92 }, "overstay": { "avgMinutes": 11 }, "noShow": { "rate": 4.2, "count": 12, "settled": 9, "lostCovers": 40 }}Guests
Section titled “Guests”GET /api/my-analytics/guests returns new-vs-returning and cross-venue
guest analytics, visit-frequency and segment breakdowns, and a ranked guest
list. Takes window, venueId, limit, offset, sort, dir, and
format=csv to stream the guest list as a file instead of JSON.
curl "https://thesidedoor.co/api/my-analytics/guests?window=90&limit=25" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/guests?window=90&limit=25", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/guests?window=90&limit=25");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "window": 90, "pii": true, "summary": { "newGuests": 420, "returningGuests": 620 }, "visitFrequency": [{ "visits": 1, "guests": 420 }], "segments": [{ "name": "Big spenders", "guests": 84 }], "guests": [{ "id": "gst_1", "name": "Sam Lee", "visits": 6, "lastVisit": "2026-07-10" }]}name/contact fields on guests are real guest data without the PII grant
they’re omitted - use fake values in your own examples regardless.
Demand funnel
Section titled “Demand funnel”GET /api/my-analytics/demand tracks the booking funnel (searched →
availability shown → checkout started → booked) by day and by hour of
week, with days and venueId.
curl "https://thesidedoor.co/api/my-analytics/demand?days=30" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/demand?days=30", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/demand?days=30");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "days": 30, "from": "2026-06-17", "to": "2026-07-17", "available": 2400, "searched": 2400, "checkoutStarted": 980, "booked": 640, "byDay": [{ "date": "2026-07-17", "searched": 90, "booked": 28 }], "byHourOfWeek": [{ "dow": 5, "hour": 19, "searched": 40, "booked": 14 }]}Reviews and vouchers
Section titled “Reviews and vouchers”GET /api/my-analytics/reviews returns a review summary (total, average
rating, how many are 4-star-and-above, a rating distribution) and a paged
list, with window, limit, offset, venueId, and q to search. GET /api/my-analytics/vouchers returns gift voucher performance: issued,
redeemed, outstanding and expired counts, redemption and breakage rates,
and revenue by currency, with days and venueId.
curl "https://thesidedoor.co/api/my-analytics/reviews?window=90&limit=25" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-analytics/vouchers?days=90" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const reviews = await fetch( "https://thesidedoor.co/api/my-analytics/reviews?window=90&limit=25", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());
const vouchers = await fetch( "https://thesidedoor.co/api/my-analytics/vouchers?days=90", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/reviews?window=90&limit=25");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$reviews = json_decode(curl_exec($ch), true);{ "window": 90, "pii": false, "summary": { "total": 212, "avgRating": 4.6, "fourPlus": 190, "fourPlusPct": 90, "lowCount": 6, "distribution": { "5": 140, "4": 50, "3": 16, "2": 4, "1": 2 } }, "reviews": [{ "id": "rev_1", "venueId": "ven_123", "venueName": "The Attic", "rating": 5, "comment": "Lovely evening", "categoryRatings": { "service": 5, "food": 5 }, "recommend": true, "createdAt": "2026-07-10T20:00:00Z" }]}{ "period": "...", "days": 90, "counts": { "issued": 140, "redeemed": 96, "outstanding": 38, "expired": 6 }, "redemptionRate": 68.6, "breakageRate": 4.3, "byCurrency": { "gbp": { "issuedPence": 700000, "redeemedPence": 480000 } }}A review row with a guest’s name or email attached (with the PII grant) is real guest data - use fake values when trying this yourself.
GET /api/my-analytics/reviews/:id/booking reads the booking a review was
left for (a table reservation, guest-list spot or event booking), with its
date, party size and a timeline of milestones (paid, checked in, closed, and
so on). It returns { "booking": null } for a review that isn’t tied to a
booking.
{ "booking": { "kind": "reservation", "ruleType": "tables", "typeLabel": "Reservation", "id": "res_123", "date": "2026-07-10", "time": "20:00", "partySize": 4, "status": "closed", "statusLabel": "Closed", "eventName": null, "tableLabel": "12", "milestones": [ { "key": "paid", "label": "Paid", "at": "2026-07-10T19:40:00Z" }, { "key": "checked_in", "label": "Checked in", "at": "2026-07-10T20:05:00Z" }, { "key": "closed", "label": "Closed", "at": "2026-07-10T22:00:00Z" } ] }}POST /api/my-analytics/reviews/:id/flag flags a review as fake, or clears
the flag. A flagged review stays in the list but is left out of every rating
(the summary above, the venue’s Sidedoor rating, and the daily rollup). Send
{ "flagged": true } to flag, or { "flagged": false } to clear it.
curl -X POST "https://thesidedoor.co/api/my-analytics/reviews/rev_1/flag" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"flagged": true}'await fetch("https://thesidedoor.co/api/my-analytics/reviews/rev_1/flag", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ flagged: true }),});$ch = curl_init("https://thesidedoor.co/api/my-analytics/reviews/rev_1/flag");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$token}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["flagged" => true]),]);curl_exec($ch);Events
Section titled “Events”GET /api/my-analytics/events returns per-event ticket performance:
sell-through, attendance and revenue, with days and venueId.
curl "https://thesidedoor.co/api/my-analytics/events?days=90" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const res = await fetch( "https://thesidedoor.co/api/my-analytics/events?days=90", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const data = await res.json();$ch = curl_init("https://thesidedoor.co/api/my-analytics/events?days=90");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$data = json_decode(curl_exec($ch), true);{ "period": "...", "days": 90, "totals": { "sold": 640, "capacity": 800, "attended": 590 }, "revenueByCurrency": { "gbp": 1280000 }, "events": [{ "id": "evt_1", "name": "Live jazz night", "date": "2026-07-20", "venue": "The Attic", "sold": 80, "capacity": 100, "attended": 74, "sellThroughPct": 80, "attendancePct": 92.5 }]}Revenue per seat-hour and weekday brief
Section titled “Revenue per seat-hour and weekday brief”GET /api/my-analytics/revpash returns revenue per available seat-hour,
per venue, each in its own currency, for one period (YYYY-MM) - it
needs the revenue permission grant. GET /api/my-analytics/brief is a
shorter weekday-trend summary (recent vs. prior period, broken down by
weekday) - the same facts Aimy reasons over when
you ask it a question in the portal. Takes venueId, days and period.
curl "https://thesidedoor.co/api/my-analytics/revpash?period=2026-07" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-analytics/brief?days=30" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const revpash = await fetch( "https://thesidedoor.co/api/my-analytics/revpash?period=2026-07", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());
const brief = await fetch( "https://thesidedoor.co/api/my-analytics/brief?days=30", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/revpash?period=2026-07");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$revpash = json_decode(curl_exec($ch), true);{ "period": "2026-07", "venues": [{ "id": "ven_123", "name": "The Attic", "currency": "gbp", "revpashPence": 420 }] }{ "brief": { "totals": { "recent": { "covers": 1920, "bookings": 640 }, "prior": { "covers": 1780, "bookings": 590 } }, "byWeekday": [{ "weekday": "Friday", "covers": 420, "bookings": 140, "noShows": 6, "revenuePence": 820000 }] }, "has_data": true}Key payment reconciliation
Section titled “Key payment reconciliation”GET /api/my-analytics/revenue-reconciliation checks that every booking
paid with keys in a month has a matching credit on your
account. It needs the revenue permission grant and access to every venue
(it can’t be scoped to one). Takes period (YYYY-MM, defaults to the
current month).
curl "https://thesidedoor.co/api/my-analytics/revenue-reconciliation?period=2026-07" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const recon = await fetch( "https://thesidedoor.co/api/my-analytics/revenue-reconciliation?period=2026-07", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/revenue-reconciliation?period=2026-07");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$recon = json_decode(curl_exec($ch), true);{ "period": "2026-07", "bookingsKeyPaid": 42, "grossKeyValuePence": 168000, "ledgerAccruals": 42, "ledgerCreditPence": 168000, "drift": 0, "ok": true}drift is bookingsKeyPaid minus ledgerAccruals; above zero means a
booking is missing its credit, and ok is false.
Exporting data
Section titled “Exporting data”GET /api/my-analytics/export downloads a bulk export of type
(reservations, guests, events, payments or vouchers) as format
(csv, default, or print for an HTML page suited to printing/PDF), over a
window of days and optionally one venueId. Each type needs its own
elevated permission grant (guest-PII for reservations/guests, revenue
for payments/vouchers), and the endpoint is rate-limited to 20 requests
per 5 minutes per account.
curl "https://thesidedoor.co/api/my-analytics/export?type=reservations&window=30" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -o reservations.csvconst res = await fetch( "https://thesidedoor.co/api/my-analytics/export?type=reservations&window=30", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` } },);const csv = await res.text();$ch = curl_init("https://thesidedoor.co/api/my-analytics/export?type=reservations&window=30");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$csv = curl_exec($ch);The response is the file itself (text/csv or text/html), not JSON - this
can contain real guest and payment data, so handle downloaded files the way
you would any other export of customer data.
The report builder
Section titled “The report builder”Beyond the ready-made metrics above, you can query your own data directly.
GET /api/my-analytics/catalog returns the field catalogue filtered to
your grants: available metrics, dimensions, filter fields, chart types,
comparison periods, and live filter-option lists (venues, events, statuses,
campaigns) - use it to construct a report spec. GET /api/my-analytics/reports lists Sidedoor’s standard one-click report
cards you’re allowed to run. POST /api/my-analytics/report runs any
report spec (a metric, a dimension, filters and a chart type - built from
the catalogue above) and returns its series and summary.
curl "https://thesidedoor.co/api/my-analytics/catalog" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl -X POST "https://thesidedoor.co/api/my-analytics/report" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"metric": "covers", "dimension": "weekday", "chart": "bar"}'const catalog = await fetch("https://thesidedoor.co/api/my-analytics/catalog", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },}).then((r) => r.json());
const report = await fetch("https://thesidedoor.co/api/my-analytics/report", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ metric: "covers", dimension: "weekday", chart: "bar" }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/report");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["metric" => "covers", "dimension" => "weekday", "chart" => "bar"]),]);$report = json_decode(curl_exec($ch), true);{ "metrics": [{ "key": "covers", "label": "Covers" }], "dimensions": [{ "key": "weekday", "label": "Day of week" }], "filterFields": [{ "key": "venue", "label": "Venue" }], "filterOptions": { "venue": [{ "id": "ven_123", "name": "The Attic" }] }, "chartTypes": ["bar", "line", "table"], "comparePeriods": ["previous_period", "previous_year"]}{ "data": [{ "id": "rep_bookings_trend", "title": "Bookings trend", "chart": "line" }] }{ "series": [{ "label": "Friday", "value": 420 }], "summary": { "total": 1920 }}Ask a question in plain language
Section titled “Ask a question in plain language”POST /api/my-analytics/nl-report uses the Aimy add-on to turn a plain
question into a validated report spec, run it, and return both - send { "question" }. It needs the add-on enabled with allowance remaining
(403 otherwise).
curl -X POST "https://thesidedoor.co/api/my-analytics/nl-report" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"question": "Which day of the week do we get the most covers?"}'const res = await fetch("https://thesidedoor.co/api/my-analytics/nl-report", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ question: "Which day of the week do we get the most covers?" }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/nl-report");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["question" => "Which day of the week do we get the most covers?"]),]);$data = json_decode(curl_exec($ch), true);{ "question": "Which day of the week do we get the most covers?", "spec": { "metric": "covers", "dimension": "weekday", "chart": "bar" }, "report": { "series": [{ "label": "Friday", "value": 420 }], "summary": { "total": 1920 } }, "tokens": 140, "cached": false, "usage": { "allowance": 5000, "used": 1520, "remaining": 3480, "unit": "tokens" }}Saved reports
Section titled “Saved reports”Save a report spec from the builder to come back to it later. GET /api/my-analytics/saved lists your saved reports (your own and ones
shared with you), filterable with owner (mine, shared or all) and
searchable with q. POST /api/my-analytics/saved saves one - send { "title", "spec", "isShared"? } where spec is a report spec of the shape
used by POST /report. PUT /api/my-analytics/saved/:id updates a saved
report’s title, spec or sharing (owner only); DELETE /api/my-analytics/saved/:id deletes it (owner only).
curl -X POST "https://thesidedoor.co/api/my-analytics/saved" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"title": "Covers by weekday", "spec": {"metric": "covers", "dimension": "weekday", "chart": "bar"}}'const saved = await fetch("https://thesidedoor.co/api/my-analytics/saved", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ title: "Covers by weekday", spec: { metric: "covers", dimension: "weekday", chart: "bar" }, }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/saved");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "title" => "Covers by weekday", "spec" => ["metric" => "covers", "dimension" => "weekday", "chart" => "bar"], ]),]);$saved = json_decode(curl_exec($ch), true);{ "reports": [{ "id": "rep_1", "title": "Covers by weekday", "spec": { "metric": "covers" }, "isShared": false, "mine": true, "updatedAt": "2026-07-17T20:00:00Z" }] }{ "ok": true, "id": "rep_1" }Dashboards
Section titled “Dashboards”A dashboard is an ordered set of saved reports shown as tiles. GET /api/my-analytics/dashboards lists your dashboards (your own and shared
ones, pinned first). POST /api/my-analytics/dashboards creates one - send
{ "title", "items": [{ "reportId", "size" }], "isShared"? }, where size
is stat, wide, wide3 or full. PUT /api/my-analytics/dashboards/:id
updates a dashboard’s title, items or sharing (owner only); DELETE /api/my-analytics/dashboards/:id deletes it (owner only). POST /api/my-analytics/dashboards/:id/pin pins or unpins it for you with { "pinned": boolean }. GET /api/my-analytics/dashboards/:id/render runs
every panel through the report engine and returns the results together -
any panel you no longer have permission for, or whose report was deleted,
is silently skipped.
curl -X POST "https://thesidedoor.co/api/my-analytics/dashboards" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"title": "Weekly review", "items": [{"reportId": "rep_1", "size": "wide"}]}'
curl "https://thesidedoor.co/api/my-analytics/dashboards/dsh_1/render" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const dashboard = await fetch("https://thesidedoor.co/api/my-analytics/dashboards", { method: "POST", headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ title: "Weekly review", items: [{ reportId: "rep_1", size: "wide" }], }),}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/dashboards");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "title" => "Weekly review", "items" => [["reportId" => "rep_1", "size" => "wide"]], ]),]);$dashboard = json_decode(curl_exec($ch), true);{ "dashboards": [{ "id": "dsh_1", "title": "Weekly review", "items": [{ "reportId": "rep_1", "size": "wide" }], "isShared": false, "mine": true, "pinned": true, "updatedAt": "2026-07-17T20:00:00Z" }] }{ "title": "Weekly review", "panels": [{ "reportId": "rep_1", "title": "Covers by weekday", "size": "wide", "series": [{ "label": "Friday", "value": 420 }] }] }Curated dashboards
Section titled “Curated dashboards”GET /api/my-analytics/prebuilt-dashboards lists Sidedoor’s own curated
dashboard templates that you have the permissions to run. GET /api/my-analytics/prebuilt-dashboards/:id/render renders one, with date
(an optional anchor trading day) and days.
curl "https://thesidedoor.co/api/my-analytics/prebuilt-dashboards" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"
curl "https://thesidedoor.co/api/my-analytics/prebuilt-dashboards/weekly-overview/render?days=7" \ -H "Authorization: Bearer $SIDEDOOR_TOKEN"const list = await fetch("https://thesidedoor.co/api/my-analytics/prebuilt-dashboards", { headers: { Authorization: `Bearer ${process.env.SIDEDOOR_TOKEN}` },}).then((r) => r.json());$ch = curl_init("https://thesidedoor.co/api/my-analytics/prebuilt-dashboards");curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {$token}"],]);$list = json_decode(curl_exec($ch), true);{ "dashboards": [{ "id": "weekly-overview", "title": "Weekly overview", "description": "Covers, revenue and no-shows for the week", "period": "7 days", "panelCount": 6 }] }{ "title": "Weekly overview", "description": "Covers, revenue and no-shows for the week", "panels": [{ "title": "Covers", "series": [{ "label": "Mon", "value": 260 }] }] }