AI Monorepo API
tRPC API Documentation
Analytics
Distinct talent, locations, and entry count for today's schedule.
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
talentCount: number // Unique talents working today
locationCount: number // Unique locations active today
scheduleCount: number // Total schedule entries today
}
curl -X GET '{{BASE_URL}}/analytics.scheduledToday?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Roster size split into on-site (active) vs off-roster (hidden) talent.
{
total: number // Total talents in the system
active: number // Talents on this company's public site (members)
hidden: number // Talents not on this company's site (off-roster)
}
curl -X GET '{{BASE_URL}}/analytics.talentCount' \
-H 'Content-Type: application/json'
Schedule count per day for the current week (Mon–Sun).
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
date: string // ISO date string
value: number // Numeric value
}>
curl -X GET '{{BASE_URL}}/analytics.weeklyBreakdown?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Distinct talent scheduled at least one day this week (Mon–Sun).
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
number
curl -X GET '{{BASE_URL}}/analytics.weeklyTalentCount?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Distinct talent and expected calls per city for the current week.
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
city: string // City name
talents: number // Distinct talent scheduled in this city this week
expectedCalls: number // Calls this week's scheduled days are expected to take here
days: Array<{
date: string // ISO date string
expectedCalls: number // Calls this day is expected to take here
}> // The seven days Mon–Sun, zero-filled where nothing is on
}>
curl -X GET '{{BASE_URL}}/analytics.weekByCity?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Calls per day since January 1 — what we took, what the roster's averages predict, and what we took on the same date a year earlier.
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
date: string // ISO date string
actual: number // Calls taken on this date
expected: number // Calls the scheduled roster's other days predict for this date
lastYear: number // Calls taken on the same calendar date a year earlier
}>
curl -X GET '{{BASE_URL}}/analytics.yearToDateCalls?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Count of roster talent missing each profile field.
Array<{
name: "Photos" | "Bio" | "Tagline" | "Age" | "Height" | "Weight" | "Measurements" | "Fees" | "Menus" | "Services" | "Notes" | "Reviews" // Profile field the completeness audit checks
talents: string[] // Nicknames of the talent missing this field, A–Z
}>
curl -X GET '{{BASE_URL}}/analytics.incompleteProfiles' \
-H 'Content-Type: application/json'
Each talent's all-time booking rhythm: calls per day worked, the same split by weekday, and the hours her calls start.
Array<{
talentId: string // UUID identifier
nickname: string // Talent display name
totalCalls: number // Bookings taken across every day worked
daysWorked: number // Distinct calendar days worked — two locations in a day is one
avgPerDay: number // Calls per day worked, 0 when she has none
busiestWeekday: number | null // Weekday with her highest average, null when she has no days
busiestHour: number | null // Hour with her most bookings, null when none carry a time
byWeekday: Array<{
weekday: number // Day of week, 0 = Sunday
calls: number // Bookings taken on this weekday
daysWorked: number // Distinct days worked on this weekday
avgPerDay: number // Calls per day worked, 0 when she never works this weekday
}> // All seven weekdays, zero-filled
byHour: Array<{
hour: number // Hour of day, 0–23
calls: number // Bookings starting within this hour
}> // All 24 hours, zero-filled
}>
curl -X GET '{{BASE_URL}}/analytics.talentStats' \
-H 'Content-Type: application/json'
Each booker's weekly reach across the roster for the last 52 weeks: calls booked over the distinct talent they were spread over, which reads her effort rather than the draw of whoever she booked.
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
bookerId: string // Unique identifier
name: string // Booker display name
bookings: number // Calls booked across the whole window
talents: number // Distinct talent booked across the whole window
avgPerTalent: number // The window's own bookings / talents — not a mean of the weeks
weeks: Array<{
weekStart: string // ISO date of the week's Monday
bookings: number // Calls she booked in this week
talents: number // Distinct talent those calls were spread over this week
avgPerTalent: number // bookings / talents, 0 when she booked nobody that week
}> // Every week in the window, zero-filled, oldest first
}>
curl -X GET '{{BASE_URL}}/analytics.bookerStats?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Announcement
Broadcasts an office announcement to every talent login as an in-app notification.
{
title: string // Headline in the feed
body: string // Full message, shown only inside the app
}
{
recipients: number
}
curl -X POST '{{BASE_URL}}/announcement.create' \
-H 'Content-Type: application/json' \
-d '{"title":"string","body":"string"}'
Past office announcements, newest first, with recipient counts.
Array<{
entityId: string // UUID identifier
title: string
body: string | null
sentAt: string | any // When the blast went out
recipients: number
}>
curl -X GET '{{BASE_URL}}/announcement.list' \
-H 'Content-Type: application/json'
Appointment
A schedule with its appointments and the derived day totals — the daily tracker detail summary (Due / Paid / Balance + the Credit/Paid/Miss state)
{
scheduleId: string // UUID identifier
}
{
schedule: {
id: string // Unique identifier
talentId: string // Unique identifier
talentName: string
locationId: string // Unique identifier
locationName: string
locationDeleted: boolean // True when the venue has been retired — the day still opens, deleting a location never moves the books
city: string
date: string | any // Date in YYYY-MM-DD format
category: "Hotel" | "Airbnb" | "Condo" // Venue type — the book-on gate (D64/D65)
talentBookOnFee: number | null // Talent's profile rate — the condo default; not used at airbnb/hotels
condoChargedToday: boolean // True when an earlier condo row on this calendar date resolved to a POSITIVE book-on — her profile rate or an operator's own figure; it does not say the profile rate was charged, and a negative reimbursement credit (D92/D99) does not count. A condo row then defaults to $0 (D112); an airbnb/hotel row is unaffected, its half being per-stay, on the stay's first day (LUX-316). The day form previews the same default the tracker applies
flatFee: number | null // Airbnb/hotel per-date room cost from the stored DailyReport, or null
bookOn: number | null // Resolved book-on — the stored snapshot, else the computed default; negative when it credits the talent for accommodation she paid herself (D92/D99)
stayExpenseId: string | null // The Accommodation expense this day's room figures were written from (LUX-316); null when typed by hand or never entered
notes?: string | null
envelopeDroppedAt: string | any | null // When the talent declared her day closed out — envelope handed in (LUX-274); read-only, moves no money
}
appointments: Array<{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
paid: number // Σ payment.amount against this appointment
}> // Appointments for a schedule, each with its own collected total
due: number // Σ(fee − discount) + service fees + book-on
paid: number // Σ payment.amount — what was collected
balance: number // Paid + applied carried credit − Due (signed, D110)
status: "credit" | "paid" | "miss"
cancellationIncome: number // Σ talent ½ of kept cancellation fees behind this day's bookings (D95) — income, separate from Due/Paid
carriedCredit: {
amount: number
from: string | any[]
}
creditPaidOut: number // Σ carried-credit payouts dated this day for this talent (LUX-341/D113) — its own line, outside Due/Paid/balance
creditRemaining: number // The credit leaving this row after the day's payouts — max(0, balance − creditPaidOut); what a Pay out can still hand back (LUX-341)
creditFate: {
spent: Array<{
date: string | any // Date in YYYY-MM-DD format
amount: number
kind: "applied" | "paidOut"
}>
carrying: number
}
settledBy: {
client: number
envelope: number
credit: number
hotelShare: number
}
}
curl -X GET '{{BASE_URL}}/appointment.bySchedule?input=%7B%22scheduleId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
A day's live appointments across every schedule — the booking grid's one read (LUX-246); cancelled and carrier rows never appear
{
date: any // The grid day
}
Array<{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
paid: number // Σ payment.amount against this appointment
durationMinutes: number | null // The menu's duration; null on an imported row without a menu
menuType: string | null // Menu type (Vip/Regular); null when imported
menuPrice: number | null // Menu catalog price; null when imported
price: number // What the client pays — menu price + extras under the VIP-inclusion rule, computed at read from today's catalog (no price is captured at write, D105)
}>
curl -X GET '{{BASE_URL}}/appointment.byDate?input=%7B%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
One appointment for the edit form
{
id: string // UUID identifier
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
}
curl -X GET '{{BASE_URL}}/appointment.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
A client's appointments in this brand (newest first) — the picker for applying a credit to a booking (LUX-113)
{
clientId: string // UUID identifier
}
Array<{
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
startTime: string | null // HH:MM, or null when unset
talentName: string
locationName: string
}>
curl -X GET '{{BASE_URL}}/appointment.byClient?input=%7B%22clientId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Books an appointment; fee and per-line service fees are captured from the menu catalog, never from the client
{
scheduleId: string // UUID identifier
clientId: string // UUID identifier
menuId: string // UUID identifier
bookedByUserId?: string // Unique identifier
phoneNumberId?: string | null
paidToUserId?: string | null
startTime?: string | null
discount: number
confirmed: boolean
isIndy: boolean
notes?: string | null
services: Array<{
menuServiceId: string // UUID identifier
}>
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
}
curl -X POST '{{BASE_URL}}/appointment.create' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","clientId":"string","menuId":"string","discount":0,"confirmed":true,"isIndy":true,"services":[{"menuServiceId":"string"}]}'
Historical import only (LUX-120): replaces a schedule-day's synthesized appointments + their payments, and upserts the day's book-on onto its daily report — one transaction, so the day lands whole or not at all (D92). The DOCUMENTED D67 EXCEPTION — the only write that accepts an explicit fee, because the sheet's total is history and re-capturing it from the talent's current Menu.fee would import today's price. Idempotent: only rows marked imported are replaced, never an operator's real booking. 404 unless the schedule belongs to the requesting company
{
scheduleId: string // UUID identifier
clientId: string // The Unknown placeholder client (D39)
bookedByUserId?: string | null // Resolved col-G booker, else null — the day owns the remainder
callCount: number // Col C — how many calls the day took; one appointment per call
serviceTotal: number // Col K — the day's fee total, split across the calls server-side
payments: Array<{
method: "EMT" | "AmazonGiftCard" | "Cash" | "TalentCredit" | "HotelShare"
amount: number
}> // The day's EMT/Amazon/Cash totals (D74), plus any talent credit settling it — ride the first call, since the sheet says only what the DAY took
bookOn: number // Col J — the day's book-on, upserted onto its DailyReport inside the same transaction so an import can never write the calls without the book-on (D92)
bookerSplit: Array<{
bookedByUserId: string // Unique identifier
calls: number // How many of the day's calls this booker took
}> // Calls within the day booked by someone other than `bookedByUserId`; the remainder stays with the day's booker
calls?: Array<{
fee: number // This call's own fee, as the source states it
startTime?: string | null // 'HH:MM' when the source records one, else null
notes?: string | null // The source line as written — duration + service, for an operator to audit against
isIndy: boolean // Indy call (LUX-239) — the talent booked it herself, so the booker earns nothing on it
menuId?: string | null // The rate this call was booked at. The source names it plainly — 'HH PSE' is the talent's 30-minute Vip row — and the SAME row already supplies `fee`, so withholding the id here only left the booking unnamed in the UI. Server-verified against the schedule's talent; null when the source gives no type
}> // The day's calls line by line, when the source has that detail. Omitted (the xlsx) keeps the even split of `serviceTotal` across `callCount`
cancellations: Array<{
fee: number // What the booking would have earned — recorded on the row, excluded from Due
deposit: number // The kept deposit, split talent/booker/agency
startTime?: string | null
notes?: string | null
method?: "EMT" | "AmazonGiftCard" | "Cash" | "TalentCredit" | "HotelShare" | null // How the deposit arrived, when the source says. Left null rather than guessed — a guessed backfill is invented finance
}> // The day's cancelled bookings whose deposit was kept — outside `callCount`/`serviceTotal` by design
}
{
replaced: number // Synthesized rows deleted before this write
dayExisted: boolean // Whether the day's report already existed — true on a re-import even with no rows to replace
appointments: Array<{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
}>
}
curl -X POST '{{BASE_URL}}/appointment.importDay' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","clientId":"string","callCount":0,"serviceTotal":0,"payments":[{"method":"EMT","amount":0}],"bookOn":0,"bookerSplit":[{"bookedByUserId":"string","calls":0}],"cancellations":[{"fee":0,"deposit":0}]}'
Edits an appointment; re-captures the fee and replaces the service lines
{
scheduleId: string // UUID identifier
clientId: string // UUID identifier
menuId: string // UUID identifier
bookedByUserId?: string // Unique identifier
phoneNumberId?: string | null
paidToUserId?: string | null
startTime?: string | null
discount?: number
confirmed?: boolean
isIndy?: boolean
notes?: string | null
services?: Array<{
menuServiceId: string // UUID identifier
}>
id: string // UUID identifier
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
}
curl -X POST '{{BASE_URL}}/appointment.update' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","clientId":"string","menuId":"string","id":"string"}'
Client-cancelled: soft-cancels the booking and, in the same transaction, moves any recorded money — kept as a validated-split cancellation fee, held as a client credit, or sent back via a refund request in the office queue (LUX-310)
{
id: string // UUID identifier
outcome: "cancel"
} | {
id: string // UUID identifier
outcome: "fee"
total: number // The recorded sum the operator saw
split: {
talent: number
booker: number
agency: number
}
} | {
id: string // UUID identifier
outcome: "credit"
total: number // The recorded sum the operator saw
} | {
id: string // UUID identifier
outcome: "refund"
total: number // The recorded sum the operator saw
refundAmount: number
refundEmail: string // Where the money goes back to — required
split?: {
talent: number
booker: number
agency: number
} // Required when total − refundAmount > 0
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
}
curl -X POST '{{BASE_URL}}/appointment.cancel' \
-H 'Content-Type: application/json' \
-d '{"id":"string","outcome":"cancel"}'
Removes an appointment
{
id: string // UUID identifier
}
curl -X POST '{{BASE_URL}}/appointment.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Schedules in a date range with the derived Due, Paid, Balance and the Credit/Paid/Miss state, computed server-side. An admin reads the whole range; a booker reads the same rows clamped to ±1 week of today (LUX-235/LUX-282)
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
scheduleId: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
city: string
calls: number // Live appointment count for the day — cancelled (D95) and carrier (LUX-206) bookings excluded
due: number // Σ(fee − discount) + Σ service fees + book-on
paid: number // Σ payment.amount — what was collected
balance: number // Paid + applied carried credit − Due (signed, D110)
status: "credit" | "paid" | "miss"
bookOn: number | null
cancellationIncome: number // Σ talent ½ of kept cancellation fees behind the day's bookings (D95)
unlinkedRoomCharge: boolean // A hotel/Airbnb day carrying a stored room figure with no Accommodation expense behind it (LUX-316) — typed by hand instead of entered once on the expense; never set on history before the link shipped
creditFate: {
spent: Array<{
date: string | any // Date in YYYY-MM-DD format
amount: number
kind: "applied" | "paidOut"
}>
carrying: number
}
settledBy: {
client: number
envelope: number
credit: number
hotelShare: number
}
}>
curl -X GET '{{BASE_URL}}/appointment.dailyTrackerList?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
One page of appointments across the company's talents, joined to its schedule for display — the browse-by-date list. Every filter (date range, talent, search) is applied server-side; omitting both date bounds is 'All dates'
{
page: number // 1-based page number
pageSize: number // Rows per page
startDate?: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate?: any // YYYY-MM-DD string parsed to UTC midnight Date
talentId?: string // Narrow to one talent (the daily-tracker deep link)
search?: string // Substring match over talent, client, location and city
}
{
items: Array<{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientId: string // Unique identifier
clientName: string // Client's name, or their code when they have none (denormalized)
menuId: string | null // null on an imported historical row
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
bookedByUserId: string | null // null when an imported row had no resolvable booker
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
bookerPay: number // Stored booker pay; rate/basis is a later phase (LUX-79)
paidToUserId: string | null // Who the booking fee goes to — the booker on shift on the appointment's day. Null while a prebook's day has no coverage written yet (wrap-up/cancellation settle it, falling back to whoever booked), or on an imported row without one
payeeName: string | null // Payee display name (denormalized); null when unresolved
confirmed: boolean // The end client has confirmed this booking
services: Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
menuServiceId: string // Unique identifier
serviceName: string // Service name (denormalized from the catalog)
fee: number // Stored per-line fee — $0 since D105 (extras are the talent's own money, never house Due); non-zero only on rows captured before D105
}>
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
isCarrier: boolean // A synthesized carrier booking (LUX-206) — exists only so a money-only day's payments have an appointment to hang on; not a call, every call count skips it
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
date: string | any // Date in YYYY-MM-DD format
talentId: string // Unique identifier
talentName: string
locationName: string
city: string
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/appointment.byDateRange?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%7D' \
-H 'Content-Type: application/json'
Auth
Authenticate with email and password
{
email: string // Email address
password: string // Account password (min 8 characters)
}
{
token: string | null // Session token
user: {
id: string // User identifier
email: string // Email address
name: string | null
image: string | null // Profile image URL
emailVerified: boolean // Whether email has been verified
}
}
curl -X POST '{{BASE_URL}}/auth.login' \
-H 'Content-Type: application/json' \
-d '{"email":"string","password":"string"}'
Booker
Returns all bookers (Users with role 'booker'), name ascending, each with its pay rate (null until set)
Array<{
id: string // Unique identifier
name: string // Booker's display name
payRate: number | null // Whole dollars paid per booked appointment; null until set (D76)
active: boolean // False once deactivated — she keeps her history but can no longer sign in or be scheduled (LUX-250)
}>
curl -X GET '{{BASE_URL}}/booker.list' \
-H 'Content-Type: application/json'
Deactivates a booker or brings her back (LUX-250). Deactivating ends her login and clears her phone coverage from `today` on; her bookings, pay rate and payroll history are untouched, and the days she already worked stay on the grid. Bookers only — any other role is NOT_FOUND. Deactivating your own account is FORBIDDEN
{
userId: string // Unique identifier
active: boolean
today: any // The caller's calendar today — deactivating clears her coverage from this date on, and leaves the days already worked. Sent by the client so the server never guesses the timezone
}
any
curl -X POST '{{BASE_URL}}/booker.setActive' \
-H 'Content-Type: application/json' \
-d '{"userId":"string","active":true,"today":"value"}'
Sets a booker's pay rate — whole dollars per booked appointment (D76); upserts by user. New appointments stamp this rate; past ones are unchanged
{
userId: string // Unique identifier
amount: number
}
curl -X POST '{{BASE_URL}}/booker.setPayRate' \
-H 'Content-Type: application/json' \
-d '{"userId":"string","amount":0}'
Per-booker Mon–Sun weeks: calls booked, pay owed (Σ stamped bookerPay), and whether the week has been paid — most recent first (D76/D77)
Array<{
userId: string // Unique identifier
bookerName: string // Booker's display name
weekStart: string | any // The week's Monday (D77)
weekEnd: string | any // The week's Sunday
calls: number // Booked appointments credited to her this week (cancelled ones excluded, D95)
amount: number // Σ the stamped bookerPay for those calls, plus the week cancellation fees (D76/D95)
cancellationPay: number // The part of `amount` that came from kept cancellation fees rather than calls (D95)
paidOn: string | any | null // The day she was paid; null until the week is confirmed
paidAmount: number | null // What was paid, snapshotted at confirm; null until confirmed
}>
curl -X GET '{{BASE_URL}}/booker.payroll' \
-H 'Content-Type: application/json'
Marks one booker's Mon–Sun week as paid, snapshotting the amount the server computes for that week; confirming the same week again overwrites rather than double-paying
{
userId: string // Unique identifier
weekStart: string | any // Date in YYYY-MM-DD format
paidOn: string | any // The day the money changed hands
}
curl -X POST '{{BASE_URL}}/booker.confirmPaid' \
-H 'Content-Type: application/json' \
-d '{"userId":"string","weekStart":"string","paidOn":"string"}'
Un-marks one booker's Mon–Sun week, deleting the payment record so the week reads as outstanding again (LUX-319). Deletes rather than zeroes: the row's existence is the paid flag, so a $0 overwrite would still assert a payout happened. A week that was never confirmed is NOT_FOUND
{
userId: string // Unique identifier
weekStart: string | any // Date in YYYY-MM-DD format
}
curl -X POST '{{BASE_URL}}/booker.markUnpaid' \
-H 'Content-Type: application/json' \
-d '{"userId":"string","weekStart":"string"}'
BookerSchedule
Returns booker coverage entries for an inclusive date range, date then startsAt ascending
{
from: any // YYYY-MM-DD string parsed to UTC midnight Date
to: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // UUID identifier
userId: string // Unique identifier
phoneNumberId: string // UUID identifier
date: string | any // Date in YYYY-MM-DD format
startsAt: string // Takeover time 'HH:MM'; '00:00' = the day's first booker
}>
curl -X GET '{{BASE_URL}}/bookerSchedule.list?input=%7B%22from%22%3A%22value%22%2C%22to%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Replaces one number+date's coverage wholesale — the final list of takeovers for that day; empty clears it
{
phoneNumberId: string // UUID identifier
date: any // YYYY-MM-DD string parsed to UTC midnight Date
shifts: Array<{
userId: string // Unique identifier
startsAt: string // Takeover time 'HH:MM'; '00:00' = the day's first booker
}>
}
any
curl -X POST '{{BASE_URL}}/bookerSchedule.setDay' \
-H 'Content-Type: application/json' \
-d '{"phoneNumberId":"string","date":"value","shifts":[{"userId":"string","startsAt":"string"}]}'
Bookings on one line's day whose stored payee is not the booker now covering that line, grouped by (paid-to-today → should-earn) with a count and both names (LUX-318). Indy calls are skipped and cancelled ones included; bookings in a Mon–Sun week either side has already been paid for are reported as `settledSkipped` rather than offered
{
phoneNumberId: string // UUID identifier
date: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
groups: Array<{
fromUserId: string // Unique identifier
fromName: string // Who those bookings are paid to today
toUserId: string // Unique identifier
toName: string // Who the day's coverage now says earns them
count: number // Bookings in this group
}>
settledSkipped: number // Drifted bookings deliberately left alone because either side of the move already has a BookerWeeklyPayment for that Mon–Sun week. Counted rather than dropped silently — an offer that quietly under-reports reads as "all clear"
}
curl -X GET '{{BASE_URL}}/bookerSchedule.payeeDrift?input=%7B%22phoneNumberId%22%3A%22string%22%2C%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Re-stamps the payee and booker pay on that line-day's drifted bookings to the booker the coverage now names (LUX-318). The drift is recomputed server-side and never read off the request — this moves money, so a stale screen cannot decide what it moves
{
phoneNumberId: string // UUID identifier
date: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
reassigned: number // Bookings whose payee and stamped booker pay were moved
}
curl -X POST '{{BASE_URL}}/bookerSchedule.reassignPayees' \
-H 'Content-Type: application/json' \
-d '{"phoneNumberId":"string","date":"value"}'
Every schedule on the day with the bookers covering its line (LUX-330): the talent's profile number while live on that day, else the venue's. Empty `coveredBy` = no line, or a line nobody covers — such a column shows for everyone
{
date: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
scheduleId: string // Unique identifier
coveredBy: string[]
}>
curl -X GET '{{BASE_URL}}/bookerSchedule.dayCoverage?input=%7B%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
The booker's landing page (LUX-335): today's talent on the lines she covers (or nobody covers) with venue, hours and her history's expectation, the distinct venues, the summed expectation, and who arrives on her lines tomorrow through today+7 — a talent's first day with no such day before it. An admin sees every line. `today` is the caller's calendar day
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
today: Array<{
scheduleId: string // Unique identifier
talentId: string // Unique identifier
nickname: string
date: string | any // Date in YYYY-MM-DD format
venue: string
city: string
startTime: string | null
endTime: string | null
expected: number | null // Her average for this weekday, null when she has no history
basis: "weekday" | "overall" // Which average `expected` came from — 'overall' is the fallback
sampleDays: number // Days worked behind `expected` — the reference point it needs
}>
locations: Array<{
venue: string
city: string
}> // Distinct venues among today's rows
expectedCalls: number // Sum of today's expectations, two decimals
arriving: Array<{
scheduleId: string // Unique identifier
talentId: string // Unique identifier
nickname: string
date: string | any // Date in YYYY-MM-DD format
venue: string
city: string
startTime: string | null
endTime: string | null
}> // First day on her lines for each talent not there the day before, tomorrow through today+7
}
curl -X GET '{{BASE_URL}}/bookerSchedule.home?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
City
Returns the requesting company's cities in display order
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // City name
sortOrder: number // Display sort order
}>
curl -X GET '{{BASE_URL}}/city.list' \
-H 'Content-Type: application/json'
Batch-update the display order of city sections for the requesting company
{
orderedIds: string[] // City IDs in the desired display order
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // City name
sortOrder: number // Display sort order
}>
curl -X POST '{{BASE_URL}}/city.reorder' \
-H 'Content-Type: application/json' \
-d '{"orderedIds":["string"]}'
Onboards a city by name for the requesting company; returns the existing city if the name is already used
{
name: string // City name
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // City name
sortOrder: number // Display sort order
}
curl -X POST '{{BASE_URL}}/city.create' \
-H 'Content-Type: application/json' \
-d '{"name":"string"}'
Renames a city owned by the requesting company; 404 unless it belongs to the company, 409 if the name is already used
{
id: string // UUID identifier
name: string // City name
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // City name
sortOrder: number // Display sort order
}
curl -X POST '{{BASE_URL}}/city.rename' \
-H 'Content-Type: application/json' \
-d '{"id":"string","name":"string"}'
Deletes a city owned by the requesting company; 404 unless it belongs to the company, 400 while it still has locations
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // City name
sortOrder: number // Display sort order
}
curl -X POST '{{BASE_URL}}/city.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Client
One page of clients (global across brands), identity fields only, with the total matching the search
{
page: number // 1-based page number
pageSize: number // Rows per page
search?: string // Substring match over name, code, phone and CAF handle
}
{
items: Array<{
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/client.list?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%7D' \
-H 'Content-Type: application/json'
Returns every client unpaginated, identity fields only — for the importer’s phone dedup index; the admin list uses `client.list`
Array<{
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
}>
curl -X GET '{{BASE_URL}}/client.listAll' \
-H 'Content-Type: application/json'
Returns the named clients in the list projection. Ids are deduped and unknown ids are OMITTED, so the result may be shorter than the input — it is never an error
{
ids: string[]
}
Array<{
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
}>
curl -X GET '{{BASE_URL}}/client.byIds?input=%7B%22ids%22%3A%5B%22string%22%5D%7D' \
-H 'Content-Type: application/json'
Returns a client by ID, with the brands they booked through
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
email: string | null // Email address
city: string | null
ageRange: string | null
ethnicity: string | null
notes: string | null
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
blacklistReason: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" | null
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
brands: string[] // Distinct company names the client has booked through
otherPhones: string[] // Their other phone numbers, E.164 each
}
curl -X GET '{{BASE_URL}}/client.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
How many times a client has booked and the few bookings that matter while answering them — upcoming soonest-first, then recent past. Across every brand, cancellations excluded. `today` is the caller's local date.
{
clientId: string // UUID identifier
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
total: number // Bookings, ever, any brand
cancelled: number // Soft-cancelled bookings, ever, any brand (LUX-310) — how often this client cancels vs attends. Deleted bookings never count
visits: Array<{
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
startTime: string | null // HH:MM, or null when unset
talentName: string
locationName: string | null
city: string | null
upcoming: boolean // Falls on the caller's today or later. Resolved here rather than by comparing clocks in the browser, and by DATE not timestamp — a booking at 10am is still today's booking at 3pm, which is exactly when an operator is asked about it
locationId: string | null // Feeds the quick chat arrival-instructions actions (LUX-211). Null when the location was deleted
}> // At most 3: every upcoming booking soonest-first, then recent past to fill. Empty when they have never booked
}
curl -X GET '{{BASE_URL}}/client.visits?input=%7B%22clientId%22%3A%22string%22%2C%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
What a client usually books, ranked most-requested first: package, extras, and the talent they book. Counts exactly what `client.visits` counts — every non-cancelled booking across every brand, upcoming included — so the two never disagree.
{
clientId: string // UUID identifier
}
{
total: number // Bookings, ever, any brand, cancellations excluded — the same figure `client.visits` reports
firstBookedAt: string | any | null // Their earliest booking ever — how long we have known them, exact regardless of how many they have. Null when they have never booked
packages: Array<{
label: string
count: number
}> // Menu booked, most-taken first, labelled "60 min VIP". A booking imported without a menu (LUX-120) counts in `total` but appears in no line here — there is no package to name
extras: Array<{
label: string
count: number
}> // Add-ons booked, most-requested first. Empty is common
talent: Array<{
label: string
count: number
lastBooked: string | any // Their latest booking with this talent. NOT necessarily in the past — a booking for Friday dates them Friday, so a caller rendering this must say "next" rather than "last" for a future date
}> // Who they have seen, most-booked first
visits: Array<{
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
startTime: string | null // HH:MM, or null when unset
talentName: string
locationName: string | null
city: string | null
menuLabel: string | null // What was booked, as "60 min VIP". Null on a booking the historical import synthesized (LUX-120), which records no package — the ranking above omits those rows for the same reason
extras: string[] // Add-ons on this booking, unranked and unordered — the ranking is `extras` above, which answers what they USUALLY take. This answers what they took that night
}> // The most recent bookings, newest first, so the same read that ranks them can also list them. Capped — `total` above is the exact figure, this is a sample of it. No `upcoming` flag: that is a function of the caller's today, and this procedure deliberately takes no `today` so its count cannot drift from `client.visits`'; a caller wanting the distinction compares `date` against its own
}
curl -X GET '{{BASE_URL}}/client.bookingProfile?input=%7B%22clientId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a client — phone is the global dedup key
{
name?: any | null
phone: string // Phone number in E.164, e.g. +14035550101 — the dedup key
email?: string | null
city?: string | null
ageRange?: "20-30" | "30-40" | "40-50" | "50+" | null
ethnicity?: string | null
notes?: string | null
cafHandle?: string | null
perbHandle?: string | null
isBlacklisted?: boolean
blacklistReason?: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" | null
isConfirmed?: boolean // Whether the client has used the service before (no deposit asked)
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
email: string | null // Email address
city: string | null
ageRange: string | null
ethnicity: string | null
notes: string | null
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
blacklistReason: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" | null
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
}
curl -X POST '{{BASE_URL}}/client.create' \
-H 'Content-Type: application/json' \
-d '{"phone":"string"}'
Updates a client
{
name?: any | null
phone?: string // Phone number in E.164, e.g. +14035550101 — the dedup key
email?: string | null
city?: string | null
ageRange?: "20-30" | "30-40" | "40-50" | "50+" | null
ethnicity?: string | null
notes?: string | null
cafHandle?: string | null
perbHandle?: string | null
isBlacklisted?: boolean
blacklistReason?: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" | null
isConfirmed?: boolean // Whether the client has used the service before (no deposit asked)
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
code: string // Immutable client code — the human identity
name: string | null // Client's full name
phone: string // Phone number in E.164, e.g. +14035550101
email: string | null // Email address
city: string | null
ageRange: string | null
ethnicity: string | null
notes: string | null
cafHandle: string | null
perbHandle: string | null
isBlacklisted: boolean // Whether the client is blacklisted across all brands
blacklistReason: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" | null
isConfirmed: boolean // Whether the client has used the service before (no deposit asked)
}
curl -X POST '{{BASE_URL}}/client.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Folds one client profile into another and retires it: bookings, credits, conversations and the Don't-See list move to the surviving client, whose code and phone are kept, and the retired profile's number stays linked so texts from it resolve to the survivor. Irreversible.
{
keepClientId: string // The profile that survives
mergeClientId: string // The profile that is retired
}
{
keptCode: string // The client code that survived
retiredCode: string // The client code that no longer exists
linkedPhone: string // The retired profile’s number, now linked to the survivor
}
curl -X POST '{{BASE_URL}}/client.merge' \
-H 'Content-Type: application/json' \
-d '{"keepClientId":"string","mergeClientId":"string"}'
Returns the ids of talents on a client's Don't-See list
{
clientId: string // UUID identifier
}
string[]
curl -X GET '{{BASE_URL}}/client.listExclusions?input=%7B%22clientId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Adds talents to a client's Don't-See list, removing none — returns how many were new
{
clientId: string // The client whose Don’t-See list is added to
talentIds: string[] // Talents who won't see this client (added; none are removed)
}
{
added: number // Rows this call created
talentIds: string[] // The whole list after the add
}
curl -X POST '{{BASE_URL}}/client.addExclusions' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string","talentIds":["string"]}'
Marks a client as confirmed, never the reverse — returns whether this call changed it
{
clientId: string // The client to mark as confirmed
}
{
promoted: boolean // Whether this call changed the client (false if already confirmed)
}
curl -X POST '{{BASE_URL}}/client.markConfirmed' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string"}'
Blocks a client who carries no reason, never re-categorizing one who does — returns whether this call changed it
{
clientId: string // The client to block, if they carry no reason
blacklistReason: "NoShow" | "TimeWaster" | "Aggressive" | "NoDeposit" | "NonCooperative" | "HygieneIssues" // The category to record — applied only when the client has none
}
{
blocked: boolean // Whether this call changed the client (false if a reason was already set)
}
curl -X POST '{{BASE_URL}}/client.markBlacklisted' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string","blacklistReason":"NoShow"}'
Places a client who has no city, never moving one who does — returns whether this call changed it
{
clientId: string // The client to place, if they carry no city
city: string // The city to record — applied only when the client has none
}
{
filled: boolean // Whether this call changed the client (false if a city was already set)
}
curl -X POST '{{BASE_URL}}/client.setCity' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string","city":"string"}'
Replaces a client's Don't-See list — the talents who won't see this client
{
clientId: string // The client whose Don’t-See list is set
talentIds: string[] // Talents who won't see this client (replaces the whole list)
}
string[]
curl -X POST '{{BASE_URL}}/client.setExclusions' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string","talentIds":["string"]}'
ClientCredit
A client's prepaid / cancellation credits in this brand, newest first
{
clientId: string // UUID identifier
}
Array<{
id: string // Unique identifier
clientId: string // Unique identifier
amount: number
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare" | null // How the deposit arrived; null on rows written before it was captured
status: "held" | "applied" | "cancellationFee"
talentAmount: number | null
bookerFee: number | null
agencyAmount: number | null
note: string | null
createdAt: string | any // ISO 8601 timestamp
appliedBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
cancelledBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
}>
curl -X GET '{{BASE_URL}}/clientCredit.byClient?input=%7B%22clientId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Records a client prepayment as a held credit (>= $50 minimum, D87) — never a Payment
{
clientId: string // UUID identifier
amount: number
method: "EMT" | "AmazonGiftCard" | "Cash"
note?: string
}
{
id: string // Unique identifier
clientId: string // Unique identifier
amount: number
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare" | null // How the deposit arrived; null on rows written before it was captured
status: "held" | "applied" | "cancellationFee"
talentAmount: number | null
bookerFee: number | null
agencyAmount: number | null
note: string | null
createdAt: string | any // ISO 8601 timestamp
appliedBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
cancelledBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
}
curl -X POST '{{BASE_URL}}/clientCredit.create' \
-H 'Content-Type: application/json' \
-d '{"clientId":"string","amount":0,"method":"EMT"}'
Applies a held credit to a booking — creates a Payment(method:Credit) for the full amount so the tracker Paid rolls up (D87)
{
id: string // UUID identifier
appointmentId: string // UUID identifier
}
{
id: string // Unique identifier
clientId: string // Unique identifier
amount: number
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare" | null // How the deposit arrived; null on rows written before it was captured
status: "held" | "applied" | "cancellationFee"
talentAmount: number | null
bookerFee: number | null
agencyAmount: number | null
note: string | null
createdAt: string | any // ISO 8601 timestamp
appliedBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
cancelledBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
}
curl -X POST '{{BASE_URL}}/clientCredit.applyToBooking' \
-H 'Content-Type: application/json' \
-d '{"id":"string","appointmentId":"string"}'
Uses a held credit as a cancellation fee behind a specific cancelled booking — stamps the talent ½ / booker $25 / agency surplus split and links the booking so the talent-day income + booker-week $25 report; no Payment (D87/D95)
{
id: string // UUID identifier
cancelledAppointmentId: string // UUID identifier
}
{
id: string // Unique identifier
clientId: string // Unique identifier
amount: number
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare" | null // How the deposit arrived; null on rows written before it was captured
status: "held" | "applied" | "cancellationFee"
talentAmount: number | null
bookerFee: number | null
agencyAmount: number | null
note: string | null
createdAt: string | any // ISO 8601 timestamp
appliedBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
cancelledBooking: {
id: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
talentName: string
locationName: string
} | any | null
}
curl -X POST '{{BASE_URL}}/clientCredit.electCancellationFee' \
-H 'Content-Type: application/json' \
-d '{"id":"string","cancelledAppointmentId":"string"}'
Removes a mistaken held credit — a consumed (applied / cancellation-fee) credit is immutable
{
id: string // UUID identifier
}
curl -X POST '{{BASE_URL}}/clientCredit.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Company
Returns all companies
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Company display name
slug: string // URL-safe unique slug, sent as the x-company-slug header
defaultPhoneNumberId: string | null // Company default (house) phone number id
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" // Which image brand this company displays; Clean = no watermark
}>
curl -X GET '{{BASE_URL}}/company.list' \
-H 'Content-Type: application/json'
Returns the requesting company's default (house) phone, or null when unset — the public fallback for talent contact numbers
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
curl -X GET '{{BASE_URL}}/company.currentDefaultPhone' \
-H 'Content-Type: application/json'
Creates a new company
{
name: string // Company display name
slug: string // URL-safe unique slug, sent as the x-company-slug header
defaultPhoneNumberId?: string | null // Company default (house) phone number id
imageBrand?: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" // Which image brand this company displays; Clean = no watermark
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Company display name
slug: string // URL-safe unique slug, sent as the x-company-slug header
defaultPhoneNumberId: string | null // Company default (house) phone number id
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" // Which image brand this company displays; Clean = no watermark
}
curl -X POST '{{BASE_URL}}/company.create' \
-H 'Content-Type: application/json' \
-d '{"name":"string","slug":"string"}'
Updates a company
{
name?: string // Company display name
slug?: string // URL-safe unique slug, sent as the x-company-slug header
defaultPhoneNumberId?: string | null // Company default (house) phone number id
imageBrand?: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" // Which image brand this company displays; Clean = no watermark
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Company display name
slug: string // URL-safe unique slug, sent as the x-company-slug header
defaultPhoneNumberId: string | null // Company default (house) phone number id
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" // Which image brand this company displays; Clean = no watermark
}
curl -X POST '{{BASE_URL}}/company.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Blocked — companies cannot be deleted via the API
{
id: string // UUID identifier
}
curl -X POST '{{BASE_URL}}/company.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Events
SSE stream that emits whenever something changed that other operators should see — an SMS, a booking, a payment, a roster edit. Carries the topic only; refetch on event.
curl -X POST '{{BASE_URL}}/events.onChange' \
-H 'Content-Type: application/json'
Expense
Ad-hoc expenses in a date range (by the date incurred), newest first — the list + the weekly report money-out
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
amount: number
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: string | any // Date in YYYY-MM-DD format
note: string | null
payrollUserId: string | null // Counts toward this user's payroll (LUX-214); null = nobody's
allocations: Array<{
locationId: string // Unique identifier
locationName: string
city: string
category: "Hotel" | "Airbnb" | "Condo" // Venue type — an Accommodation at a hotel/Airbnb is a stay (LUX-316)
amount: number
}>
stay: {
scheduleId: string // Unique identifier
talentId: string // Unique identifier
talentName: string
date: string | any // Date in YYYY-MM-DD format
bookOn: number
} | null // The first-day report this room bill charges, or null — an Accommodation at a hotel with no stay charges no talent
}>
curl -X GET '{{BASE_URL}}/expense.byDateRange?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
One expense with its allocations, or null when not this company’s
{
id: string // UUID identifier
}
{
id: string // Unique identifier
amount: number
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: string | any // Date in YYYY-MM-DD format
note: string | null
payrollUserId: string | null // Counts toward this user's payroll (LUX-214); null = nobody's
allocations: Array<{
locationId: string // Unique identifier
locationName: string
city: string
category: "Hotel" | "Airbnb" | "Condo" // Venue type — an Accommodation at a hotel/Airbnb is a stay (LUX-316)
amount: number
}>
stay: {
scheduleId: string // Unique identifier
talentId: string // Unique identifier
talentName: string
date: string | any // Date in YYYY-MM-DD format
bookOn: number
} | null // The first-day report this room bill charges, or null — an Accommodation at a hotel with no stay charges no talent
} | null
curl -X GET '{{BASE_URL}}/expense.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Records an expense across 0/1/many locations — the server splits the amount evenly (0 locations = a general cost); allocation shares are derived, never client-sent. A payroll association must be someone who can be paid: talent and deactivated logins are BAD_REQUEST (LUX-302). With `stayScheduleId`, also writes the stay's first-day book-on (½ the amount, LUX-316) in the same transaction — BAD_REQUEST at a condo or a venue the expense is not for, CONFLICT when that day already carries another bill
{
amount: number // Whole dollars
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: any // YYYY-MM-DD string parsed to UTC midnight Date
note?: string
locationIds: string[]
payrollUserId?: string | null
stayScheduleId?: string | null
}
{
id: string // Unique identifier
amount: number
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: string | any // Date in YYYY-MM-DD format
note: string | null
payrollUserId: string | null // Counts toward this user's payroll (LUX-214); null = nobody's
allocations: Array<{
locationId: string // Unique identifier
locationName: string
city: string
category: "Hotel" | "Airbnb" | "Condo" // Venue type — an Accommodation at a hotel/Airbnb is a stay (LUX-316)
amount: number
}>
stay: {
scheduleId: string // Unique identifier
talentId: string // Unique identifier
talentName: string
date: string | any // Date in YYYY-MM-DD format
bookOn: number
} | null // The first-day report this room bill charges, or null — an Accommodation at a hotel with no stay charges no talent
}
curl -X POST '{{BASE_URL}}/expense.create' \
-H 'Content-Type: application/json' \
-d '{"amount":0,"category":"Admin","incurredOn":"value","locationIds":["string"]}'
Updates an expense and replaces its allocation; 404 unless it belongs to the requesting company. Changing the payroll association re-checks it (LUX-302); re-sending the stored one does not, so an expense already tagged to talent stays editable
{
amount: number // Whole dollars
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: any // YYYY-MM-DD string parsed to UTC midnight Date
note?: string
locationIds?: string[]
payrollUserId?: string | null
stayScheduleId?: string | null
id: string // UUID identifier
}
{
id: string // Unique identifier
amount: number
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: string | any // Date in YYYY-MM-DD format
note: string | null
payrollUserId: string | null // Counts toward this user's payroll (LUX-214); null = nobody's
allocations: Array<{
locationId: string // Unique identifier
locationName: string
city: string
category: "Hotel" | "Airbnb" | "Condo" // Venue type — an Accommodation at a hotel/Airbnb is a stay (LUX-316)
amount: number
}>
stay: {
scheduleId: string // Unique identifier
talentId: string // Unique identifier
talentName: string
date: string | any // Date in YYYY-MM-DD format
bookOn: number
} | null // The first-day report this room bill charges, or null — an Accommodation at a hotel with no stay charges no talent
}
curl -X POST '{{BASE_URL}}/expense.update' \
-H 'Content-Type: application/json' \
-d '{"amount":0,"category":"Admin","incurredOn":"value","id":"string"}'
A talent's schedule days at one venue within ±6 months of a date, with each day's stored room figures — the expense form derives the stay and its first day from these (LUX-316)
{
locationId: string // UUID identifier
talentId: string // UUID identifier
around: any // ±6 months of this date
}
Array<{
scheduleId: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
flatFee: number | null
storedBookOn: number | null
stayExpenseId: string | null // The expense whose stay already sits on this day, if any
}>
curl -X GET '{{BASE_URL}}/expense.stayDays?input=%7B%22locationId%22%3A%22string%22%2C%22talentId%22%3A%22string%22%2C%22around%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Removes an expense and its allocations; a stay's first-day charge goes with it (LUX-316)
{
id: string // UUID identifier
}
curl -X POST '{{BASE_URL}}/expense.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
TalentCreditPayout
Records cash handed back to a talent against her carried credit on a date (LUX-341) — stops the carry from that date, shows on the day page outside Due/Paid/balance, and is money out in the payout week on its own line
{
talentId: string // UUID identifier
date: any // YYYY-MM-DD string parsed to UTC midnight Date
amount: number
note?: string
}
{
id: string // Unique identifier
talentId: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
amount: number
note: string | null
createdAt: string | any // ISO 8601 timestamp
}
curl -X POST '{{BASE_URL}}/talentCreditPayout.create' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string","date":"value","amount":0}'
FinanceDashboard
Owner business-health roll-up over a period (day / week / month / quarter / year): gross (Σ Due), expenses (ad-hoc + prorated fixed costs), net, and collected cash — each with a delta vs the prior period, plus a trend, by-city and by-category breakdown
{
periodKind: "day" | "week" | "month" | "quarter" | "year"
anchorDate: string // The caller's calendar today (YYYY-MM-DD) — the period is resolved around it, sent by the client so the server never guesses the timezone
}
{
periodKind: "day" | "week" | "month" | "quarter" | "year"
range: {
startDate: string | any // Date in YYYY-MM-DD format
endDate: string | any // Date in YYYY-MM-DD format
}
kpis: {
gross: {
value: number
previous: number
delta: number
} // Revenue owed — Σ Due (D85)
expenses: {
value: number
previous: number
delta: number
} // Ad-hoc expenses + prorated fixed costs + booker pay (D91)
net: {
value: number
previous: number
delta: number
} // Gross − expenses — accrual (D85)
collectedCash: {
value: number
previous: number
delta: number
} // Payments collected in-period
cashProfit: {
value: number
previous: number
delta: number
} // collectedCash − expenses − talent credit paid out (D113) — the cash-basis profit the client keeps in her own P&L (D91)
}
trend: Array<{
date: string | any // The bucket end date — the series x-axis label
gross: number
expenses: number
net: number
collectedCash: number
}>
byCity: Array<{
city: string
gross: number
spend: number // What the city cost over the period — every location in it, hotels included
net: number
}>
businessWide: number // Unallocated ad-hoc expenses (admin, owner payouts) tied to no city — the by-city column reconciles as company net = Σ(city nets) − businessWide (D101)
byCategory: Array<{
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation" | "Rent" | "Utilities" | "Internet" | "Insurance" | "Misc"
amount: number
}> // The "Where the money went" breakdown: every ExpenseCategory (Cleaning & Supplies combine their one-off + monthly-fixed halves, D102) plus the fixed-only lines (rent/utilities/internet/insurance/misc). Σ(amount) + bookerPay == the Expenses total, so no cost is missing from the chart (LUX-155 #1)
bookerPay: number // Σ the stamped Appointment.bookerPay for the period (D76/D91) — its own expense line beside byCategory, since it is not an ExpenseCategory
}
curl -X GET '{{BASE_URL}}/financeDashboard.byPeriod?input=%7B%22periodKind%22%3A%22day%22%2C%22anchorDate%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Per-location leaderboard over a period: each non-Hotel location’s revenue (Σ Due), prorated fixed costs, allocated expenses, profit and booked-day utilization — ranked client-side by profit / margin / profit-per-booked-day
{
periodKind: "day" | "week" | "month" | "quarter" | "year"
anchorDate: string // The caller's calendar today (YYYY-MM-DD) — the period is resolved around it, sent by the client so the server never guesses the timezone
}
Array<{
locationId: string // Unique identifier
locationName: string
city: string
revenue: number // Σ Due for the period (D85), signed
collectedCash: number // Σ payments taken against the period's bookings — the client's Paid (D91/D98)
fixedCosts: number // LocationFixedCost prorated to the period by day-overlap (D86)
allocatedExpenses: number // Σ ExpenseAllocation.amount (D80)
bookerPay: number // Σ the stamped Appointment.bookerPay for the period (D76/D91)
profit: number // Earned: revenue − fixedCosts − allocatedExpenses − bookerPay; negative in a loss
cashProfit: number // Cash: collectedCash − the same expenses (D91/D98) — what the rail ranks on
bookedDays: number // Distinct Schedule dates for the location in the period (D88)
daysInPeriod: number // Calendar days in the period — the utilization denominator
}>
curl -X GET '{{BASE_URL}}/financeDashboard.locations?input=%7B%22periodKind%22%3A%22day%22%2C%22anchorDate%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
One location’s monthly trend (revenue vs expenses), cost composition (fixed lines + assigned expenses), cumulative investment-vs-return (payback), and booked-day utilization over the last 12 months
{
locationId: string // UUID identifier
anchorDate: string // The caller's calendar today (YYYY-MM-DD) — the series ends at its month, sent by the client so the server never guesses the timezone
}
{
locationId: string // Unique identifier
locationName: string
city: string
months: Array<{
period: string // Period in YYYY-MM format
revenue: number // Σ Due for the month (D85), signed
expenses: number // fixedCosts + allocatedExpenses + bookerPay (D91)
fixedCosts: number // Σ LocationFixedCost lines for the month
allocatedExpenses: number // Σ ExpenseAllocation.amount — supplies & assigned expenses (D80)
bookerPay: number // Σ the stamped Appointment.bookerPay for the month (D76/D91)
bookedDays: number
daysInMonth: number
}>
costComposition: {
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
allocatedExpenses: number // Σ ExpenseAllocation.amount over the window — assigned supplies & expenses (D80)
bookerPay: number // Σ the stamped Appointment.bookerPay over the window (D76/D91)
}
costCompositionByMonth: Array<{
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
allocatedExpenses: number // Σ ExpenseAllocation.amount over the window — assigned supplies & expenses (D80)
bookerPay: number // Σ the stamped Appointment.bookerPay over the window (D76/D91)
period: string // Period in YYYY-MM format
}> // Per-month cost breakdown for the stacked composition chart (D89)
investmentReturn: Array<{
period: string // Period in YYYY-MM format
cumulativeInvested: number // Running Σ (fixedCosts + allocatedExpenses + bookerPay) to date (D91)
cumulativeRevenue: number // Running Σ revenue (Σ Due, D85) to date
}>
}
curl -X GET '{{BASE_URL}}/financeDashboard.locationDetail?input=%7B%22locationId%22%3A%22string%22%2C%22anchorDate%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
DailyReport
Upserts a schedule-day's book-on (+ the airbnb/hotel per-date flat fee). The book-on is stored as sent — an editable per-day override (D64). 404 unless the schedule belongs to the requesting company
{
scheduleId: string // UUID identifier
flatFee?: number | null
bookOn: number
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
flatFee: number | null // Airbnb/hotel per-date room cost; null for a condo
bookOn: number // The book-on that applied for the day (the stored snapshot, D15/D16); negative when it credits the talent for accommodation she paid herself (D92/D99)
}
curl -X POST '{{BASE_URL}}/dailyReport.save' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","bookOn":0}'
File
Returns root images with failed processing status
any[]
curl -X GET '{{BASE_URL}}/file.listFailed' \
-H 'Content-Type: application/json'
Retries processing for a failed image by re-generating missing variants
{
imageId: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
fileId: string // Unique identifier
talentId: string // Unique identifier
variant: "Original" | "Thumbnail" | "Large"
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
status: "Processing" | "Completed" | "Failed"
errorMessage: string | null
width: number | null // Image width in pixels
height: number | null // Image height in pixels
sortOrder: number // Display sort order
sourceImageId: string | null
file: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
size: number // File size in bytes
blobKey: string // Blob key within Azure container
container: string // Azure Blob Storage container name
url: string | null // Public or SAS URL
uploadedById: string | null
} | null
variants: any[]
}
curl -X POST '{{BASE_URL}}/file.retryProcessing' \
-H 'Content-Type: application/json' \
-d '{"imageId":"string"}'
Debug
Throws a test error for Sentry
curl -X GET '{{BASE_URL}}/debug.sentryTest' \
-H 'Content-Type: application/json'
Image
Returns all images for a talent belonging to the requesting company
{
talentId: string // UUID identifier
}
any[]
curl -X GET '{{BASE_URL}}/image.byTalent?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns talentId → thumbnail URL for the requesting company's talents, in the company's image brand
Record<string, string | null>
curl -X GET '{{BASE_URL}}/image.thumbnails' \
-H 'Content-Type: application/json'
Returns talentId → thumbnail URL across all companies' talents, in the requesting company's image brand — the admin lists show non-members too
Record<string, string | null>
curl -X GET '{{BASE_URL}}/image.thumbnailsAll' \
-H 'Content-Type: application/json'
Creates a new image
{
fileId: string // ID of the uploaded File
variant?: "Original" | "Thumbnail" | "Large"
imageBrand?: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
width?: number | null // Image width in pixels
height?: number | null // Image height in pixels
sortOrder?: number // Display sort order
talentId: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
fileId: string // Unique identifier
talentId: string // Unique identifier
variant: "Original" | "Thumbnail" | "Large"
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
status: "Processing" | "Completed" | "Failed"
errorMessage: string | null
width: number | null // Image width in pixels
height: number | null // Image height in pixels
sortOrder: number // Display sort order
sourceImageId: string | null
file: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
size: number // File size in bytes
blobKey: string // Blob key within Azure container
container: string // Azure Blob Storage container name
url: string | null // Public or SAS URL
uploadedById: string | null
} | null
variants: any[]
}
curl -X POST '{{BASE_URL}}/image.create' \
-H 'Content-Type: application/json' \
-d '{"fileId":"string","talentId":"string"}'
Updates an existing image
{
width?: number | null // Image width in pixels
height?: number | null // Image height in pixels
sortOrder?: number // Display sort order
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
fileId: string // Unique identifier
talentId: string // Unique identifier
variant: "Original" | "Thumbnail" | "Large"
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
status: "Processing" | "Completed" | "Failed"
errorMessage: string | null
width: number | null // Image width in pixels
height: number | null // Image height in pixels
sortOrder: number // Display sort order
sourceImageId: string | null
file: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
size: number // File size in bytes
blobKey: string // Blob key within Azure container
container: string // Azure Blob Storage container name
url: string | null // Public or SAS URL
uploadedById: string | null
} | null
variants: any[]
}
curl -X POST '{{BASE_URL}}/image.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Batch-update sort order for a set of images
{
orderedIds: string[] // Image IDs in the desired display order
}
any[]
curl -X POST '{{BASE_URL}}/image.reorder' \
-H 'Content-Type: application/json' \
-d '{"orderedIds":["string"]}'
Returns a short-lived SAS URL for downloading the original blob
{
id: string // UUID identifier
}
{
url: string // Short-lived SAS URL for downloading the blob
}
curl -X GET '{{BASE_URL}}/image.downloadUrl?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Re-derives a talent's photos for every brand their companies display (e.g. the clean set after adding a LUX model to VIP Kitty). Idempotent — only missing brand variants are rendered.
{
talentId: string // UUID identifier
}
{
rendered: number // Root images that got a missing brand
skipped: number // Roots skipped (no backing blob/uploader)
failed: number // Roots that errored during render/upload
}
curl -X POST '{{BASE_URL}}/image.reprocessForTalent' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string"}'
Deletes an image and its variants from DB and blob storage
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
fileId: string // Unique identifier
talentId: string // Unique identifier
variant: "Original" | "Thumbnail" | "Large"
imageBrand: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
status: "Processing" | "Completed" | "Failed"
errorMessage: string | null
width: number | null // Image width in pixels
height: number | null // Image height in pixels
sortOrder: number // Display sort order
sourceImageId: string | null
file: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
size: number // File size in bytes
blobKey: string // Blob key within Azure container
container: string // Azure Blob Storage container name
url: string | null // Public or SAS URL
uploadedById: string | null
} | null
variants: any[]
}
curl -X POST '{{BASE_URL}}/image.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Location
Returns the requesting company's locations
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}>
curl -X GET '{{BASE_URL}}/location.list' \
-H 'Content-Type: application/json'
Returns every live location as a schedule-grid bucket (id, display name, city), ordered city-then-location, with no schedule entries. Condos past their move-out date are excluded (LUX-240) — an ended tenancy takes no new bookings; weeks where it has rows still show it via schedule.weekEntriesAdmin. Takes no date range: it is the same for every week, so the schedule page paints its location list before any week loads and keeps it across week switches. Pair with schedule.weekEntriesAdmin, which supplies that week's days.
Array<{
locationId: string // Unique identifier
locationName: string // Display name for the location (already formatted)
city: string
cityId: string // City record ID (for reordering city sections)
}>
curl -X GET '{{BASE_URL}}/location.listForScheduleGrid' \
-H 'Content-Type: application/json'
Returns a location by ID; 404 unless it belongs to the requesting company
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X GET '{{BASE_URL}}/location.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a location owned by the requesting company
{
city: string // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName?: string | null
unitNumber?: string | null
street?: string | null
province?: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null
postalCode?: string | "" | null
arrivalInstructions1?: string | null // Arrival instructions — Location #1
arrivalInstructions2?: string | null // Arrival instructions — Location #2
emergencyPlan?: string | null // Emergency plan (shut-offs, electrical panel, …)
garbageLocation?: string | null // Where the garbage goes
checkInInstructions?: string | null // Check-in instructions
checkOutInstructions?: string | null // Check-out instructions
safeLocation?: string | null // Where the safe is
parkingInstructions?: string | null // Parking instructions
wifi?: string | null // WiFi network and password
additionalInfo?: string | null // Additional info
houseGuidelines?: string[] // House guidelines, in display order
faq?: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
capacity?: number // Max talent scheduled per day
phoneNumberId?: string | null // Phone number record ID
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X POST '{{BASE_URL}}/location.create' \
-H 'Content-Type: application/json' \
-d '{"city":"string","name":"string","category":"Hotel"}'
Batch-update the display order of locations within a city for the requesting company
{
orderedIds: string[] // Location IDs in the desired display order (within one city)
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}>
curl -X POST '{{BASE_URL}}/location.reorder' \
-H 'Content-Type: application/json' \
-d '{"orderedIds":["string"]}'
Updates a location owned by the requesting company
{
city?: string // City name
name?: string // Location name within the city
category?: "Hotel" | "Airbnb" | "Condo"
hotelName?: string | null
unitNumber?: string | null
street?: string | null
province?: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null
postalCode?: string | "" | null
arrivalInstructions1?: string | null // Arrival instructions — Location #1
arrivalInstructions2?: string | null // Arrival instructions — Location #2
emergencyPlan?: string | null // Emergency plan (shut-offs, electrical panel, …)
garbageLocation?: string | null // Where the garbage goes
checkInInstructions?: string | null // Check-in instructions
checkOutInstructions?: string | null // Check-out instructions
safeLocation?: string | null // Where the safe is
parkingInstructions?: string | null // Parking instructions
wifi?: string | null // WiFi network and password
additionalInfo?: string | null // Additional info
houseGuidelines?: string[] // House guidelines, in display order
faq?: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
capacity?: number // Max talent scheduled per day
phoneNumberId?: string | null // Phone number record ID
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X POST '{{BASE_URL}}/location.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Ends a condo's tenancy at a move-out date, or resumes it with null — a dated deactivation, never a delete (D104). Condo only: Airbnb/Hotel are always active
{
id: string // UUID identifier
tenancyEndedAt: any | null // Move-out date (YYYY-MM-DD), or null to resume the tenancy
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X POST '{{BASE_URL}}/location.endTenancy' \
-H 'Content-Type: application/json' \
-d '{"id":"string","tenancyEndedAt":"value"}'
Returns a location's emergency-plan pictures/videos (LUX-212); scoped to the requesting company
{
locationId: string // UUID identifier
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
file: {
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
url: string | null // Public blob URL
}
}>
curl -X GET '{{BASE_URL}}/location.emergencyMedia?input=%7B%22locationId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Deletes one emergency-plan picture/video, including its blob; 404 unless its location belongs to the requesting company
{
id: string // UUID identifier
}
{
id: string // Unique identifier
locationId: string // Unique identifier
file: {
name: string // Original file name
mimeType: string // MIME type (e.g. image/jpeg)
url: string | null // Public blob URL
}
}
curl -X POST '{{BASE_URL}}/location.deleteEmergencyMedia' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a location owned by the requesting company
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X POST '{{BASE_URL}}/location.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Folds one location into another while imported duplicates are cleaned up (LUX-265): every schedule, note, cost and expense on `mergeLocationId` moves to `keepLocationId`, then the retired location is soft-deleted so finance keeps its history. A schedule day the same talent already has at the survivor keeps the survivor's daily report and takes the retired day's bookings; a fixed-cost month or expense allocation held by both is summed. NOT_FOUND unless both are live locations of the requesting company
{
keepLocationId: string // The location that survives
mergeLocationId: string // The location that is retired
}
{
keptName: string // The location that survived
retiredName: string // The location that is now deleted
schedulesMoved: number // Schedule days moved to the surviving location
}
curl -X POST '{{BASE_URL}}/location.merge' \
-H 'Content-Type: application/json' \
-d '{"keepLocationId":"string","mergeLocationId":"string"}'
The requesting company's soft-deleted locations, recoverable via location.restore (LUX-203)
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any // When the location was deleted
}>
curl -X GET '{{BASE_URL}}/location.listDeleted' \
-H 'Content-Type: application/json'
Restores a soft-deleted location owned by the requesting company (LUX-203); NOT_FOUND when the id is not deleted or not owned
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
city: string | any // City name
name: string // Location name within the city
category: "Hotel" | "Airbnb" | "Condo"
hotelName: string | null // Hotel name (operator-only)
unitNumber: string | null // Unit / suite number
street: string | null // Street address
province: "AB" | "BC" | "MB" | "NB" | "NL" | "NS" | "NT" | "NU" | "ON" | "PE" | "QC" | "SK" | "YT" | null // Province / territory
postalCode: string | null // Postal code (reported as stored)
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
capacity: number // Max talent scheduled per day
arrivalInstructions1: string | null
arrivalInstructions2: string | null
emergencyPlan: string | null
garbageLocation: string | null
checkInInstructions: string | null
checkOutInstructions: string | null
safeLocation: string | null
parkingInstructions: string | null
wifi: string | null
additionalInfo: string | null
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}> // FAQ question/answer pairs, in display order
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
}
curl -X POST '{{BASE_URL}}/location.restore' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
LocationFixedCost
A location's operating costs for a month (YYYY-MM), or null when none are set
{
locationId: string // UUID identifier
period: string // Period in YYYY-MM format
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
locationId: string // Owning location ID
period: string // Period in YYYY-MM format
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
} | null
curl -X GET '{{BASE_URL}}/locationFixedCost.byPeriod?input=%7B%22locationId%22%3A%22string%22%2C%22period%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
The nearest month with costs — the most recent one before the given month, or failing that the nearest later one (backfill) — the source for "copy latest month"; null when no other month has costs
{
locationId: string // UUID identifier
period: string // Period in YYYY-MM format
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
locationId: string // Owning location ID
period: string // Period in YYYY-MM format
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
} | null
curl -X GET '{{BASE_URL}}/locationFixedCost.latest?input=%7B%22locationId%22%3A%22string%22%2C%22period%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Upserts a month's operating costs; 404 unless the location belongs to the requesting company
{
locationId: string // UUID identifier
period: string // Period in YYYY-MM format
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
locationId: string // Owning location ID
period: string // Period in YYYY-MM format
rent: number // Amount in whole dollars
utilities: number // Amount in whole dollars
internet: number // Amount in whole dollars
insurance: number // Amount in whole dollars
cleaning: number // Amount in whole dollars
supplies: number // Amount in whole dollars
misc: number // Amount in whole dollars
}
curl -X POST '{{BASE_URL}}/locationFixedCost.save' \
-H 'Content-Type: application/json' \
-d '{"locationId":"string","period":"string","rent":0,"utilities":0,"internet":0,"insurance":0,"cleaning":0,"supplies":0,"misc":0}'
LocationNote
Returns the requesting company's location notes whose date falls in the window, scoped via location ownership.
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
locationId: string // Unique identifier
date: string | any // Note date
text: string // Note text
createdByName: string | null // Display name of the user who wrote it; null for a legacy row
}>
curl -X GET '{{BASE_URL}}/locationNote.week?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Every add, edit and delete a location's day has seen — including notes that were deleted. Newest first, scoped via location ownership.
{
locationId: string // UUID identifier
date: any // Note date
}
Array<{
id: string // Unique identifier
noteId: string // Unique identifier
action: "Created" | "Updated" | "Deleted" // What happened to the note
text: string // Note text
createdByName: string | null // Display name of the user who wrote it; null for a legacy row
createdAt: string | any // When the change was made
}>
curl -X GET '{{BASE_URL}}/locationNote.history?input=%7B%22locationId%22%3A%22string%22%2C%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Creates a note pinned to one of the requesting company's locations on a specific day.
{
locationId: string // UUID identifier
date: any // Note date
text: string // Note text
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
locationId: string // Unique identifier
date: string | any // Note date
text: string // Note text
createdByName: string | null // Display name of the user who wrote it; null for a legacy row
}
curl -X POST '{{BASE_URL}}/locationNote.create' \
-H 'Content-Type: application/json' \
-d '{"locationId":"string","date":"value","text":"string"}'
Updates a note on one of the requesting company's locations.
{
text?: string // Note text
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
locationId: string // Unique identifier
date: string | any // Note date
text: string // Note text
createdByName: string | null // Display name of the user who wrote it; null for a legacy row
}
curl -X POST '{{BASE_URL}}/locationNote.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a note on one of the requesting company's locations.
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
locationId: string // Unique identifier
date: string | any // Note date
text: string // Note text
createdByName: string | null // Display name of the user who wrote it; null for a legacy row
}
curl -X POST '{{BASE_URL}}/locationNote.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
LocationPnl
Per-location profit for a month on both bases — earned (Σ Due, incl. book-on) and cash (Σ Paid) — each less fixed costs, allocated expenses and booker pay, plus the off-roster reconciling line
{
period: string // Period in YYYY-MM format
}
{
rows: Array<{
locationId: string // Unique identifier
locationName: string
city: string
revenue: number // Σ Due for the month — the FEE owed incl. book-on (D85)
collectedCash: number // Σ payments taken against the month's bookings — the client's Paid (D91/D98)
fixedCosts: number // Σ LocationFixedCost lines for the period
allocatedExpenses: number // Σ ExpenseAllocation.amount for the month
bookerPay: number // Σ the stamped Appointment.bookerPay for the month (D76/D91)
profit: number // Earned: revenue − fixedCosts − allocatedExpenses − bookerPay; negative in a loss month
cashProfit: number // Cash: collectedCash − the same expenses — the client's own Profit = Paid − Expenses (D91/D98)
}> // One row per location on the brand's books, for the month
offRoster: {
revenue: number // Σ Due for the month — the FEE owed incl. book-on (D85)
collectedCash: number // Σ payments taken against the month's bookings — the client's Paid (D91/D98)
fixedCosts: number // Σ LocationFixedCost lines for the period
allocatedExpenses: number // Σ ExpenseAllocation.amount for the month
bookerPay: number // Σ the stamped Appointment.bookerPay for the month (D76/D91)
profit: number // Earned: revenue − fixedCosts − allocatedExpenses − bookerPay; negative in a loss month
cashProfit: number // Cash: collectedCash − the same expenses — the client's own Profit = Paid − Expenses (D91/D98)
} // Σ the money of the locations the roster omits — the reconciling line (D91)
}
curl -X GET '{{BASE_URL}}/locationPnl.byPeriod?input=%7B%22period%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Notification
One page of the signed-in user’s notifications, newest first.
{
page: number // 1-based page number
pageSize: number // Rows per page
}
{
items: Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "SmsReceived" | "TalentChangeRequested" | "BookingCreated" | "Announcement" | "TalentChangeDecided"
entityId: string // Id of the entity the type points at, e.g. an SMS thread
title: string // Snapshot taken when the event happened
body: string | null
readAt: string | any | null // Null while unread
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/notification.list?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%7D' \
-H 'Content-Type: application/json'
One of the signed-in user's notifications by id — null for anyone else's.
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "SmsReceived" | "TalentChangeRequested" | "BookingCreated" | "Announcement" | "TalentChangeDecided"
entityId: string // Id of the entity the type points at, e.g. an SMS thread
title: string // Snapshot taken when the event happened
body: string | null
readAt: string | any | null // Null while unread
} | null
curl -X GET '{{BASE_URL}}/notification.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
How many notifications the signed-in user has not read.
{
count: number
}
curl -X GET '{{BASE_URL}}/notification.unreadCount' \
-H 'Content-Type: application/json'
Marks one of the signed-in user’s notifications read. Idempotent.
{
id: string // UUID identifier
}
any
curl -X POST '{{BASE_URL}}/notification.markRead' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Marks every unread notification of the signed-in user.
any
curl -X POST '{{BASE_URL}}/notification.markAllRead' \
-H 'Content-Type: application/json'
VAPID public key the browser subscribes under; null while web push is unconfigured.
{
publicKey: string | null
}
curl -X GET '{{BASE_URL}}/notification.pushPublicKey' \
-H 'Content-Type: application/json'
Registers this browser’s push subscription for the signed-in user. Idempotent per endpoint.
{
endpoint: string
keys: {
p256dh: string
auth: string
}
}
any
curl -X POST '{{BASE_URL}}/notification.subscribePush' \
-H 'Content-Type: application/json' \
-d '{"endpoint":"string","keys":{"p256dh":"string","auth":"string"}}'
Removes this browser’s push subscription. Another user’s endpoint is a silent no-op.
{
endpoint: string
}
any
curl -X POST '{{BASE_URL}}/notification.unsubscribePush' \
-H 'Content-Type: application/json' \
-d '{"endpoint":"string"}'
Fires one diagnostic web push at every device the user has subscribed — no notification row, no gates. The counts say which leg failed.
{
userId: string // Unique identifier
}
{
configured: boolean // False while VAPID env is unset
devices: number // Push subscriptions the user has
delivered: number // Sends the push service accepted
}
curl -X POST '{{BASE_URL}}/notification.sendTestPush' \
-H 'Content-Type: application/json' \
-d '{"userId":"string"}'
SSE stream that emits whenever the signed-in user gets a notification.
curl -X POST '{{BASE_URL}}/notification.onNew' \
-H 'Content-Type: application/json'
Payment
An appointment's recorded payment lines (the Payments card) — one or many per appointment, e.g. an e-transfer + cash split
{
appointmentId: string // UUID identifier
}
Array<{
id: string // Unique identifier
appointmentId: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
emtSenderName: string | null // The name the e-transfer came under — null on non-EMT lines and on EMT lines where it was not keyed
}>
curl -X GET '{{BASE_URL}}/payment.byAppointment?input=%7B%22appointmentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Records a payment line against an appointment. Only the first line may be a deposit; the client comes from the appointment, never sent
{
appointmentId: string // UUID identifier
method: "EMT" | "AmazonGiftCard" | "Cash"
amount: number // Whole dollars collected
isDeposit: boolean
emtSenderName?: string // The name the e-transfer came under
}
{
id: string // Unique identifier
appointmentId: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
emtSenderName: string | null // The name the e-transfer came under — null on non-EMT lines and on EMT lines where it was not keyed
}
curl -X POST '{{BASE_URL}}/payment.create' \
-H 'Content-Type: application/json' \
-d '{"appointmentId":"string","method":"EMT","amount":0,"isDeposit":true}'
Records the talent's day envelope — cash or e-transfer for the whole day — against a schedule-day (LUX-206, LUX-340), whether or not the day has bookings; a client's own payment for a call goes on the call via Record Payment. The day's carrier booking is found-or-created (the import's money-only-day rule) and the line lands on it; carriers are skipped by every call count
{
scheduleId: string // UUID identifier
method: "Cash" | "EMT"
amount: number // Whole dollars collected
emtSenderName?: string // The name the e-transfer came under
}
{
id: string // Unique identifier
appointmentId: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
emtSenderName: string | null // The name the e-transfer came under — null on non-EMT lines and on EMT lines where it was not keyed
}
curl -X POST '{{BASE_URL}}/payment.createForSchedule' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","method":"Cash","amount":0}'
Removes a payment line
{
id: string // UUID identifier
}
curl -X POST '{{BASE_URL}}/payment.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Payroll
The requesting user's own payroll: their booker Mon–Sun weeks (same aggregation as the admin Booker Payroll screen, filtered to them) and the expenses tagged to their payroll — never anyone else's
{
weeks: Array<{
userId: string // Unique identifier
bookerName: string // Booker's display name
weekStart: string | any // The week's Monday (D77)
weekEnd: string | any // The week's Sunday
calls: number // Booked appointments credited to her this week (cancelled ones excluded, D95)
amount: number // Σ the stamped bookerPay for those calls, plus the week cancellation fees (D76/D95)
cancellationPay: number // The part of `amount` that came from kept cancellation fees rather than calls (D95)
paidOn: string | any | null // The day she was paid; null until the week is confirmed
paidAmount: number | null // What was paid, snapshotted at confirm; null until confirmed
}> // The requesting user’s own booker weeks, most recent first
expenses: Array<{
id: string // Unique identifier
amount: number
category: "Admin" | "Cleaning" | "Photoshoot" | "Marketing" | "Owner" | "Supplies" | "InitialSetup" | "Accommodation"
incurredOn: string | any // Date in YYYY-MM-DD format
note: string | null
payrollUserId: string | null // Counts toward this user's payroll (LUX-214); null = nobody's
allocations: Array<{
locationId: string // Unique identifier
locationName: string
city: string
category: "Hotel" | "Airbnb" | "Condo" // Venue type — an Accommodation at a hotel/Airbnb is a stay (LUX-316)
amount: number
}>
stay: {
scheduleId: string // Unique identifier
talentId: string // Unique identifier
talentName: string
date: string | any // Date in YYYY-MM-DD format
bookOn: number
} | null // The first-day report this room bill charges, or null — an Accommodation at a hotel with no stay charges no talent
}> // Expenses tagged to the requesting user’s payroll, newest first
}
curl -X GET '{{BASE_URL}}/payroll.me' \
-H 'Content-Type: application/json'
PhoneNumber
Returns all phone numbers
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
}>
curl -X GET '{{BASE_URL}}/phoneNumber.list' \
-H 'Content-Type: application/json'
Returns a phone number by ID
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
}
curl -X GET '{{BASE_URL}}/phoneNumber.getById?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a new phone number
{
name?: string | null
number: string // Phone number (stored as 10 NANP national digits)
usedForMessages?: boolean // Whether this number is used for messages
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
}
curl -X POST '{{BASE_URL}}/phoneNumber.create' \
-H 'Content-Type: application/json' \
-d '{"number":"string"}'
Updates an existing phone number
{
name?: string | null
number?: string // Phone number (stored as 10 NANP national digits)
usedForMessages?: boolean // Whether this number is used for messages
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
}
curl -X POST '{{BASE_URL}}/phoneNumber.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a phone number
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
}
curl -X POST '{{BASE_URL}}/phoneNumber.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Review
Returns all reviews for a talent, newest first
{
talentId: string // UUID identifier
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
talentId: string // Unique identifier
content: string // Review content (rich-text HTML)
createdByName: string | null // Display name of admin who created the review
}>
curl -X GET '{{BASE_URL}}/review.byTalent?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a new talent review
{
talentId: string // UUID identifier
content: string // Review content (rich-text HTML)
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
talentId: string // Unique identifier
content: string // Review content (rich-text HTML)
createdByName: string | null // Display name of admin who created the review
}
curl -X POST '{{BASE_URL}}/review.create' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string","content":"string"}'
Updates a review
{
content: string // Review content (rich-text HTML)
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
talentId: string // Unique identifier
content: string // Review content (rich-text HTML)
createdByName: string | null // Display name of admin who created the review
}
curl -X POST '{{BASE_URL}}/review.update' \
-H 'Content-Type: application/json' \
-d '{"content":"string","id":"string"}'
Deletes a review
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
talentId: string // Unique identifier
content: string // Review content (rich-text HTML)
createdByName: string | null // Display name of admin who created the review
}
curl -X POST '{{BASE_URL}}/review.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Schedule
Returns the requesting company's schedules for today with talent data
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
talentId: string // Unique identifier
talent: {
id: string // Unique identifier
nickname: string
}
}>
curl -X GET '{{BASE_URL}}/schedule.forToday?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns pre-grouped visiting-soon entries (next 60 days)
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
talentId: string // Unique identifier
nickname: string
city: string
dateLabel: string // Formatted date range, e.g. "Mar.16-22"
}>
curl -X GET '{{BASE_URL}}/schedule.visitingSoon?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns the requesting company's schedules in a date range with talent and location data
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
talentId: string // Unique identifier
talent: {
id: string // Unique identifier
nickname: string
}
}>
curl -X GET '{{BASE_URL}}/schedule.byDateRange?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns a week of the requesting company's schedules pre-grouped by city → location → date. Drops soft-deleted-location rows.
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
city: string
locations: Array<{
locationId: string // Unique identifier
locationName: string // Display name for the location (already formatted)
deleted: boolean // Always `false` on the public path (filtered server-side)
days: Array<{
date: string | any // Date in YYYY-MM-DD format
entries: Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
talentId: string // Unique identifier
talent: {
id: string // Unique identifier
nickname: string
}
}>
}>
}>
}>
curl -X GET '{{BASE_URL}}/schedule.weekGrid?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns all schedules for a talent
{
talentId: string // UUID identifier
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}>
curl -X GET '{{BASE_URL}}/schedule.byTalent?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns all schedules for a location
{
locationId: string // UUID identifier
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}>
curl -X GET '{{BASE_URL}}/schedule.byLocation?input=%7B%22locationId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns a week's schedule entries bucketed by date, one bucket per POPULATED location — the locations without rows come from location.listForScheduleGrid. Soft-deleted locations appear only when they have rows in the window (flagged deleted), carrying their city so the client can place them.
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
locationId: string // Unique identifier
locationName: string // Display name for the location (already formatted)
city: string
cityId: string // City record ID (for reordering city sections)
deleted: boolean // True when the location is soft-deleted (has rows in window)
days: Array<{
date: string | any // Date in YYYY-MM-DD format
entries: Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
talentId: string // Unique identifier
talent: {
id: string // Unique identifier
nickname: string
}
}>
}>
}>
curl -X GET '{{BASE_URL}}/schedule.weekEntriesAdmin?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
How booked each scheduled day in the window is: the live calls actually taken on it, and what the talent's history says to expect there. One row per schedule, keyed by `scheduleId` so it joins onto schedule.weekEntriesAdmin. `expected` is null for a talent who has never worked — an absence, not a forecast of none.
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
scheduleId: string // Unique identifier
booked: number // Live calls on this scheduled day; cancelled ones excluded
expected: number | null // Her average for this weekday, null when she has no history
basis: "weekday" | "overall" // Which average `expected` came from — 'overall' is the fallback
sampleDays: number // Days worked behind `expected` — the reference point it needs
}>
curl -X GET '{{BASE_URL}}/schedule.weekBookingsAdmin?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
A location's all-time booking history: every talent who has worked it with her calls-per-day there, best first, and each weekday's norm (null under three samples — an absence, not a norm of zero). Feeds the schedule grid's day hover card.
{
locationId: string // UUID identifier
}
{
talent: Array<{
talentId: string // Unique identifier
nickname: string // Talent display name
avgHere: number // Calls per day worked at this location, all time
daysHere: number // Days worked at this location
}> // Everyone who has worked here, best average first
weekdays: Array<{
weekday: number // Day of week, 0 = Sunday
avg: number | null // Calls this location takes on this weekday, null under 3 samples
sampleDays: number // Distinct dates of this weekday behind `avg`
}> // All seven weekdays, zero-filled
}
curl -X GET '{{BASE_URL}}/schedule.locationHistoryAdmin?input=%7B%22locationId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Every location that has ever been staffed, ranked by all-time calls per staffed calendar date, best first. Feeds the Talent dashboard location list; pair with schedule.locationHistoryAdmin for one location’s detail.
Array<{
locationId: string // Unique identifier
name: string // Location display name
avgPerDay: number // Calls per staffed calendar date here, all time
daysWorked: number // Distinct calendar dates anyone worked here
}>
curl -X GET '{{BASE_URL}}/schedule.locationRankingAdmin' \
-H 'Content-Type: application/json'
Returns the selected company's schedules in a date range (admin; location-ownership, no roster gate)
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
talentId: string // Unique identifier
talent: {
id: string // Unique identifier
nickname: string
}
}>
curl -X GET '{{BASE_URL}}/schedule.byDateRangeAll?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Creates a new schedule
{
locationId: string // UUID identifier
date: any // Schedule date
startTime?: string | null // Start time
endTime?: string | null // End time
notes?: string | null // Optional notes
talentId: string // UUID identifier
}
{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}
curl -X POST '{{BASE_URL}}/schedule.create' \
-H 'Content-Type: application/json' \
-d '{"locationId":"string","date":"value","talentId":"string"}'
Get-or-create a schedule by its natural key (talent + location + date). Idempotent — returns the row and whether it was newly created. The historical import (LUX-120) uses this as its per-day anchor.
{
locationId: string // UUID identifier
date: any // Schedule date
startTime?: string | null // Start time
endTime?: string | null // End time
notes?: string | null // Optional notes
talentId: string // UUID identifier
}
{
created: boolean // True when a new schedule row was created
schedule: {
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}
}
curl -X POST '{{BASE_URL}}/schedule.upsert' \
-H 'Content-Type: application/json' \
-d '{"locationId":"string","date":"value","talentId":"string"}'
Updates an existing schedule. Passing `talentId` re-points the day to another talent (LUX-188) — its appointments and money hang off the schedule, so they follow. Rejects a re-point onto an occupied (talent, location, date) with CONFLICT rather than merging two days.
{
talentId?: string // UUID identifier
locationId?: string // UUID identifier
date?: any // Schedule date
startTime?: string | null // Start time
endTime?: string | null // End time
notes?: string | null // Optional notes
id: string // UUID identifier
}
{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}
curl -X POST '{{BASE_URL}}/schedule.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a schedule
{
id: string // UUID identifier
}
{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}
curl -X POST '{{BASE_URL}}/schedule.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Service
Returns all services with talent counts
Array<{
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
talentCount: number // Number of talent profiles using this service
}>
curl -X GET '{{BASE_URL}}/service.list' \
-H 'Content-Type: application/json'
Returns a service by ID
{
id: string // UUID identifier
}
{
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
curl -X GET '{{BASE_URL}}/service.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a new service
{
name: string // Service name
}
{
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
curl -X POST '{{BASE_URL}}/service.create' \
-H 'Content-Type: application/json' \
-d '{"name":"string"}'
Updates a service
{
name?: string // Service name
id: string // UUID identifier
}
{
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
curl -X POST '{{BASE_URL}}/service.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a service
{
id: string // UUID identifier
}
{
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
curl -X POST '{{BASE_URL}}/service.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
MessageRewrite
Returns all message rewrite rules
Array<{
id: string // Unique identifier
word: string | null
replacement: string // Carrier-safe text sent in its place
serviceId: string | null // Linked service, if any
createdAt: string | any // Record creation timestamp
service: {
id: string // UUID identifier
name: string
} | null // Linked service whose current name is the matched word
}>
curl -X GET '{{BASE_URL}}/messageRewrite.list' \
-H 'Content-Type: application/json'
Returns a message rewrite rule by ID
{
id: string // UUID identifier
}
{
id: string // Unique identifier
word: string | null
replacement: string // Carrier-safe text sent in its place
serviceId: string | null // Linked service, if any
createdAt: string | any // Record creation timestamp
service: {
id: string // UUID identifier
name: string
} | null // Linked service whose current name is the matched word
}
curl -X GET '{{BASE_URL}}/messageRewrite.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a new message rewrite rule
{
word?: string | null
replacement: string // Carrier-safe text sent in its place
serviceId?: string | null
}
{
id: string // Unique identifier
word: string | null
replacement: string // Carrier-safe text sent in its place
serviceId: string | null // Linked service, if any
createdAt: string | any // Record creation timestamp
service: {
id: string // UUID identifier
name: string
} | null // Linked service whose current name is the matched word
}
curl -X POST '{{BASE_URL}}/messageRewrite.create' \
-H 'Content-Type: application/json' \
-d '{"replacement":"string"}'
Updates a message rewrite rule
{
word?: string | null
replacement: string // Carrier-safe text sent in its place
serviceId?: string | null
id: string // UUID identifier
}
{
id: string // Unique identifier
word: string | null
replacement: string // Carrier-safe text sent in its place
serviceId: string | null // Linked service, if any
createdAt: string | any // Record creation timestamp
service: {
id: string // UUID identifier
name: string
} | null // Linked service whose current name is the matched word
}
curl -X POST '{{BASE_URL}}/messageRewrite.update' \
-H 'Content-Type: application/json' \
-d '{"replacement":"string","id":"string"}'
Deletes a message rewrite rule
{
id: string // UUID identifier
}
{
id: string // Unique identifier
word: string | null
replacement: string // Carrier-safe text sent in its place
serviceId: string | null // Linked service, if any
createdAt: string | any // Record creation timestamp
service: {
id: string // UUID identifier
name: string
} | null // Linked service whose current name is the matched word
}
curl -X POST '{{BASE_URL}}/messageRewrite.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Sms
One page of SMS messages, newest first, optionally filtered to one of our lines or one client. Renders both the inbox and a single thread.
{
page: number // 1-based page number
pageSize: number // Rows per page
phoneNumberId?: string // Only messages on this line of ours
clientId?: string // Only messages with this client
}
{
items: Array<{
id: string // Unique identifier
threadId: string // The conversation it belongs to. Read it off a reply rather than assuming: a send after a number change opens the thread the answer will arrive in.
direction: "Inbound" | "Outbound"
status: "Received" | "Queued" | "Sent" | "Delivered" | "Undelivered" | "Failed"
errorCode: number | null // The provider's error code on a failed delivery — Telnyx 40300; historical rows carry Twilio codes like 21610
fromNumber: string // Sender at the time it was sent
toNumber: string // Recipient at the time it was sent — our line on an inbound, the client on an outbound
body: string
readAt: string | any | null // Global read state, not per-user
createdAt: string | any // ISO 8601 timestamp
client: {
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null
phone: string // Phone number in E.164, e.g. +14035550101
isConfirmed: boolean
isBlacklisted: boolean // Standing, carried here so a rail of conversations can show it without a query per row
hasLinkedNumbers: boolean // Whether this client texts from more than one number
} | null // The client who sent it, as they are now
phoneNumber: {
id: string // Unique identifier
name: string | null // Line label, e.g. the city
number: string
} | null // The line it arrived on, as it is now
sentBy: {
id: string // Unique identifier
name: string
} | null // The user who sent it from the portal; null for anything inbound
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/sms.list?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%7D' \
-H 'Content-Type: application/json'
One page of conversations — a client and one of our lines — most recently active first, each with its newest message and unread count.
{
page: number // 1-based page number
pageSize: number // Rows per page
phoneNumberId?: string // Only conversations on this line of ours
search?: string // Matches a client's name, code or phone
}
{
items: Array<{
id: string // Unique identifier
clientId: string | null // null = sender was unattributable
phoneNumberId: string | null // null = not one of our lines
unreadCount: number // Inbound and still unread, in this conversation
lastMessage: {
id: string // Unique identifier
threadId: string // The conversation it belongs to. Read it off a reply rather than assuming: a send after a number change opens the thread the answer will arrive in.
direction: "Inbound" | "Outbound"
status: "Received" | "Queued" | "Sent" | "Delivered" | "Undelivered" | "Failed"
errorCode: number | null // The provider's error code on a failed delivery — Telnyx 40300; historical rows carry Twilio codes like 21610
fromNumber: string // Sender at the time it was sent
toNumber: string // Recipient at the time it was sent — our line on an inbound, the client on an outbound
body: string
readAt: string | any | null // Global read state, not per-user
createdAt: string | any // ISO 8601 timestamp
client: {
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null
phone: string // Phone number in E.164, e.g. +14035550101
isConfirmed: boolean
isBlacklisted: boolean // Standing, carried here so a rail of conversations can show it without a query per row
hasLinkedNumbers: boolean // Whether this client texts from more than one number
} | null // The client who sent it, as they are now
phoneNumber: {
id: string // Unique identifier
name: string | null // Line label, e.g. the city
number: string
} | null // The line it arrived on, as it is now
sentBy: {
id: string // Unique identifier
name: string
} | null // The user who sent it from the portal; null for anything inbound
} // Newest message — carries the client, the line and the preview
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/sms.listThreads?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%7D' \
-H 'Content-Type: application/json'
Inbound messages nobody has read yet, across the whole inbox — the sidebar badge. Read state is global.
{
count: number
}
curl -X GET '{{BASE_URL}}/sms.unreadCount' \
-H 'Content-Type: application/json'
One conversation, oldest-first within the page. Page 1 is the most recent messages.
{
page: number // 1-based page number
pageSize: number // Rows per page
threadId: string // UUID identifier
}
{
items: Array<{
id: string // Unique identifier
threadId: string // The conversation it belongs to. Read it off a reply rather than assuming: a send after a number change opens the thread the answer will arrive in.
direction: "Inbound" | "Outbound"
status: "Received" | "Queued" | "Sent" | "Delivered" | "Undelivered" | "Failed"
errorCode: number | null // The provider's error code on a failed delivery — Telnyx 40300; historical rows carry Twilio codes like 21610
fromNumber: string // Sender at the time it was sent
toNumber: string // Recipient at the time it was sent — our line on an inbound, the client on an outbound
body: string
readAt: string | any | null // Global read state, not per-user
createdAt: string | any // ISO 8601 timestamp
client: {
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null
phone: string // Phone number in E.164, e.g. +14035550101
isConfirmed: boolean
isBlacklisted: boolean // Standing, carried here so a rail of conversations can show it without a query per row
hasLinkedNumbers: boolean // Whether this client texts from more than one number
} | null // The client who sent it, as they are now
phoneNumber: {
id: string // Unique identifier
name: string | null // Line label, e.g. the city
number: string
} | null // The line it arrived on, as it is now
sentBy: {
id: string // Unique identifier
name: string
} | null // The user who sent it from the portal; null for anything inbound
}>
total: number // Rows matching the query, across all pages
}
curl -X GET '{{BASE_URL}}/sms.threadMessages?input=%7B%22page%22%3A0%2C%22pageSize%22%3A0%2C%22threadId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
The non-message rows of one conversation, oldest first — the client's bookings and payments. Client-scoped, not thread-scoped: nothing records which conversation an event belongs to, so a client who texts two of our lines sees the same event on both. Imported rows are excluded from both, cancelled bookings from the bookings; a payment on a cancelled booking still shows, and system-created credit payments do not.
{
threadId: string // UUID identifier
}
{
events: Array<{
type: "booking" | "payment" // What happened
id: string // The row itself — a booking carries a deposit and a balance, so two pills of one appointment differ only here
at: string | any // When it was recorded — where the pill sits in the conversation
appointmentId: string // The booking the pill opens — for a payment, the one it settles
talentName: string // Talent's nickname (denormalized)
date: string | any // The day booked, which is rarely the day it was booked on
startTime?: string | null // Bookings only — HH:MM when one was set, else absent. Never a schedule default: that is the talent's working hours, not a time anyone chose for this booking
amount?: number // Payments only — whole dollars collected
isDeposit?: boolean // Payments only — balance settled later (D34)
method?: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare" // Payments only — the rail it arrived on; never `Credit` here
}> // Oldest first. Empty for a thread whose sender was never identified — an event belongs to a client, and that thread has none
}
curl -X GET '{{BASE_URL}}/sms.threadEvents?input=%7B%22threadId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Clears the unread flag on every inbound message in one conversation. Read state is global — the inbox is one shared queue.
{
threadId: string // UUID identifier
}
{
count: number // Messages newly marked read
}
curl -X POST '{{BASE_URL}}/sms.markThreadRead' \
-H 'Content-Type: application/json' \
-d '{"threadId":"string"}'
Sends a text to a client from one of our lines and records it against the sending user.
{
threadId: string // UUID identifier
body: string // Message text — 1600 is the provider’s hard ceiling per message
}
{
id: string // Unique identifier
threadId: string // The conversation it belongs to. Read it off a reply rather than assuming: a send after a number change opens the thread the answer will arrive in.
direction: "Inbound" | "Outbound"
status: "Received" | "Queued" | "Sent" | "Delivered" | "Undelivered" | "Failed"
errorCode: number | null // The provider's error code on a failed delivery — Telnyx 40300; historical rows carry Twilio codes like 21610
fromNumber: string // Sender at the time it was sent
toNumber: string // Recipient at the time it was sent — our line on an inbound, the client on an outbound
body: string
readAt: string | any | null // Global read state, not per-user
createdAt: string | any // ISO 8601 timestamp
client: {
id: string // Unique identifier
code: string // Immutable client code — the human identity
name: string | null
phone: string // Phone number in E.164, e.g. +14035550101
isConfirmed: boolean
isBlacklisted: boolean // Standing, carried here so a rail of conversations can show it without a query per row
hasLinkedNumbers: boolean // Whether this client texts from more than one number
} | null // The client who sent it, as they are now
phoneNumber: {
id: string // Unique identifier
name: string | null // Line label, e.g. the city
number: string
} | null // The line it arrived on, as it is now
sentBy: {
id: string // Unique identifier
name: string
} | null // The user who sent it from the portal; null for anything inbound
}
curl -X POST '{{BASE_URL}}/sms.reply' \
-H 'Content-Type: application/json' \
-d '{"threadId":"string","body":"string"}'
Tag
Returns all tags
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}>
curl -X GET '{{BASE_URL}}/tag.list' \
-H 'Content-Type: application/json'
Returns a tag by ID
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
curl -X GET '{{BASE_URL}}/tag.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Creates a new tag
{
name: string // Tag name
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
curl -X POST '{{BASE_URL}}/tag.create' \
-H 'Content-Type: application/json' \
-d '{"name":"string"}'
Updates a tag
{
name?: string // Tag name
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
curl -X POST '{{BASE_URL}}/tag.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a tag
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
curl -X POST '{{BASE_URL}}/tag.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Talent
Returns the requesting company’s talents for the public roster, including service names for the filter UI
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
images: any[]
menuServices: Array<{
service: {
name: string
}
}>
}>
curl -X GET '{{BASE_URL}}/talent.list' \
-H 'Content-Type: application/json'
Returns the requesting company’s talents with images, sorted with today's available first
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
images: any[]
}>
curl -X GET '{{BASE_URL}}/talent.featured?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns the requesting company’s talents (with images) scheduled for today, including the city they are in today
{
today: any // YYYY-MM-DD string parsed to UTC midnight Date
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
images: any[]
city: string // City the talent is in today
startTime: string | null // Today's start time (HH:MM) if known
endTime: string | null // Today's end time (HH:MM) if known
}>
curl -X GET '{{BASE_URL}}/talent.availableToday?input=%7B%22today%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Returns a talent by ID with relations; 404 unless the talent belongs to the requesting company
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
defaultPhoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
phoneNumber2?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
menus: Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "Regular" | "Vip"
durationMinutes: number // Session duration in minutes
price: number // Price in whole dollars
}>
menuServices: Array<{
id: string // Unique identifier
serviceId: string // Unique identifier
excludedFromPromo: boolean // Excluded from promo package
includedInVip: boolean // Included in all VIP appointments
price: number | null
service: {
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
hiddenAt?: string | any | null // Set when the service was retired from her menu (LUX-343, D114). Only reads that ask for retired rows ever see a value.
}>
schedules: Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}>
images: any[]
}
curl -X GET '{{BASE_URL}}/talent.byId?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns a talent by nickname with relations; 404 unless the talent belongs to the requesting company
{
nickname: string
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
defaultPhoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
phoneNumber2?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
menus: Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "Regular" | "Vip"
durationMinutes: number // Session duration in minutes
price: number // Price in whole dollars
}>
menuServices: Array<{
id: string // Unique identifier
serviceId: string // Unique identifier
excludedFromPromo: boolean // Excluded from promo package
includedInVip: boolean // Included in all VIP appointments
price: number | null
service: {
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
hiddenAt?: string | any | null // Set when the service was retired from her menu (LUX-343, D114). Only reads that ask for retired rows ever see a value.
}>
schedules: Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}>
images: any[]
}
curl -X GET '{{BASE_URL}}/talent.byNickname?input=%7B%22nickname%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns lightweight talent list (all companies) with a `member` flag for the requesting company
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
badge: "Visiting" | "Star" | "Vip" | "New" | null
tagline: string | null // Short promotional headline
member: boolean // Member of the requesting company (shown on its public site)
companies: string[] // Slugs of every company she is a member of — lets the client decide brand-specific things (which public site to link) without an extra request
}>
curl -X GET '{{BASE_URL}}/talent.listSummary' \
-H 'Content-Type: application/json'
Returns a talent by nickname across all companies, with a `member` flag for the requesting company
{
nickname: string
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
defaultPhoneNumber?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
phoneNumber2?: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tags: Array<{
tag: {
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
name: string // Tag name
}
}>
menus: Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "Regular" | "Vip"
durationMinutes: number // Session duration in minutes
price: number // Price in whole dollars
}>
menuServices: Array<{
id: string // Unique identifier
serviceId: string // Unique identifier
excludedFromPromo: boolean // Excluded from promo package
includedInVip: boolean // Included in all VIP appointments
price: number | null
service: {
id: string // Unique identifier
name: string // Service name
createdAt: string | any // Record creation timestamp
}
hiddenAt?: string | any | null // Set when the service was retired from her menu (LUX-343, D114). Only reads that ask for retired rows ever see a value.
}>
schedules: Array<{
id: string // Unique identifier
locationId: string // Unique identifier
date: string | any // Schedule date
startTime: string | null // Start time
endTime: string | null // End time
notes: string | null // Optional notes
location: {
id: string // Unique identifier
city: string | any // City name
name: string // Location name within the city
phoneNumberId: string | null // Phone number record ID
phoneNumber?: {
id: string // Unique identifier
name: string | null
number: string // Phone number
usedForMessages: boolean // Whether this number is used for messages
} | null
tenancyEndedAt: string | any | null // Condo move-out date; null while the tenancy is live
deletedAt: string | any | null
}
}>
images: any[]
member: boolean // Member of the requesting company (shown on its public site)
}
curl -X GET '{{BASE_URL}}/talent.byNicknameAll?input=%7B%22nickname%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Admin-only forum-ad notes for a talent
{
id: string // UUID identifier
}
{
notes: string | null // Admin-only forum-ad template
}
curl -X GET '{{BASE_URL}}/talent.notesById?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Returns the companies a talent belongs to
{
talentId: string // UUID identifier
}
Array<{
companyId: string // Company the talent belongs to
companySlug: string // URL-safe unique slug, sent as the x-company-slug header
companyName: string // Company display name
}>
curl -X GET '{{BASE_URL}}/talent.memberships?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Adds a talent to or removes a talent from a company; returns the updated membership list
{
talentId: string // Talent to add to or remove from a company
companyId: string // Target company
member: boolean // true adds the membership, false removes it
}
Array<{
companyId: string // Company the talent belongs to
companySlug: string // URL-safe unique slug, sent as the x-company-slug header
companyName: string // Company display name
}>
curl -X POST '{{BASE_URL}}/talent.setMembership' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string","companyId":"string","member":true}'
Returns the ids of clients on a talent's Don't-See list
{
talentId: string // UUID identifier
}
string[]
curl -X GET '{{BASE_URL}}/talent.listExclusions?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Replaces a talent's Don't-See list — the clients this talent won't see
{
talentId: string // The talent whose Don't-See list is set
clientIds: string[] // Clients this talent won't see (replaces the whole list)
}
string[]
curl -X POST '{{BASE_URL}}/talent.setExclusions' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string","clientIds":["string"]}'
Returns the linked login's phone number — null means the talent cannot sign in to the staff portal
{
talentId: string // UUID identifier
}
{
phoneNumber: string | null // The linked login's phone — null means no staff-portal access
}
curl -X GET '{{BASE_URL}}/talent.login?input=%7B%22talentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Sets or removes the phone number this talent signs in to the staff portal with (LUX-276). Setting a number mints a login if she has none; removing it revokes her sessions
{
talentId: string // The talent whose portal login is set
phoneNumber: any | null // Login phone in E.164 — null removes staff-portal access
}
{
phoneNumber: string | null // The linked login's phone — null means no staff-portal access
}
curl -X POST '{{BASE_URL}}/talent.setLoginPhone' \
-H 'Content-Type: application/json' \
-d '{"talentId":"string","phoneNumber":"value"}'
The requesting user's own talent profile — the talent linked to their account, never anyone else's; null for an admin with no talent link. Carries the slugs of every brand she is a member of, so the portal can show brand-specific fields
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
bookingPushEnabled: boolean // Her switch on the booking push (LUX-283)
companies: string[] // Slugs of every company she is a member of
} | null
curl -X GET '{{BASE_URL}}/talent.me' \
-H 'Content-Type: application/json'
The requesting talent's own Mon–Sun week: each scheduled day's venue, shift times, live booking count, her own book-on fee, and her line for the day with the bookers covering it
{
weekStart: any // Any day of the wanted week — resolved to its Mon–Sun bounds server-side
}
{
startDate: string | any // Date in YYYY-MM-DD format
endDate: string | any // Date in YYYY-MM-DD format
days: Array<{
scheduleId: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
locationId: string // Deep-link key into the talent's Locations screen
line: string | null // Her line for the day (LUX-331): her profile number while live on the day, else the venue's — the no-hint fallback of the resolution that picks a booking's payee (`bookingLine`, LUX-328); null only when neither her profile nor the venue supplies one
lineCoverage: Array<{
startsAt: string // Takeover time 'HH:MM'; '00:00' = the day's first booker
bookerName: string // The booker's work name
}> // Who covers her line that day, earliest takeover first; empty when nobody does
locationName: string
city: string
street: string | null // Street address; null when unset
startTime: string | null // HH:MM shift start, or null
endTime: string | null // HH:MM shift end, or null
bookingCount: number // Live bookings that day — cancelled and carrier rows excluded
bookOnFee: number | null // Her own book-on fee for the day, in whole dollars — the flat fee off her profile's fees tab; null when the office has not set one
envelopeDroppedAt: string | any | null // When she declared the day closed out — envelope handed in (LUX-274); null while open
}> // The week's scheduled days, earliest first
}
curl -X GET '{{BASE_URL}}/talent.mySchedule?input=%7B%22weekStart%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
The requesting talent's own scheduled days over one calendar month — the Tours calendar's schedule side (LUX-268)
{
month: string // The wanted calendar month, YYYY-MM
}
{
startDate: string | any // Date in YYYY-MM-DD format
endDate: string | any // Date in YYYY-MM-DD format
days: Array<{
scheduleId: string // Unique identifier
date: string | any // Date in YYYY-MM-DD format
locationId: string // Deep-link key into the talent's Locations screen
line: string | null // Her line for the day (LUX-331): her profile number while live on the day, else the venue's — the no-hint fallback of the resolution that picks a booking's payee (`bookingLine`, LUX-328); null only when neither her profile nor the venue supplies one
lineCoverage: Array<{
startsAt: string // Takeover time 'HH:MM'; '00:00' = the day's first booker
bookerName: string // The booker's work name
}> // Who covers her line that day, earliest takeover first; empty when nobody does
locationName: string
city: string
street: string | null // Street address; null when unset
startTime: string | null // HH:MM shift start, or null
endTime: string | null // HH:MM shift end, or null
bookingCount: number // Live bookings that day — cancelled and carrier rows excluded
bookOnFee: number | null // Her own book-on fee for the day, in whole dollars — the flat fee off her profile's fees tab; null when the office has not set one
envelopeDroppedAt: string | any | null // When she declared the day closed out — envelope handed in (LUX-274); null while open
}> // The week's scheduled days, earliest first
}
curl -X GET '{{BASE_URL}}/talent.myTourCalendar?input=%7B%22month%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
The venues on the requesting talent's own roster, each with its talent-facing content: getting-in text, parking, WiFi, garbage and safe locations, house guidelines, FAQ and the emergency plan with its pictures
Array<{
id: string // Unique identifier
name: string
city: string // The venue's city — the design's area line
street: string | null // Street address; null when unset
unitNumber: string | null // Unit / suite number
phone: string | null // The venue's own line (the ☎ call target); null when unset
checkInInstructions: string | null // The "getting in" text; null until the office writes it
checkOutInstructions: string | null // The "heading out" text; null until the office writes it
parkingInstructions: string | null // Where she parks; null until the office writes it
wifi: string | null // The venue's network and password; null until the office writes it
garbageLocation: string | null // Where the garbage goes; null until the office writes it
safeLocation: string | null // Where the safe is; null until the office writes it
houseGuidelines: string[] // House guidelines, in display order
faq: Array<{
question: string
answer: string
}>
emergencyPlan: string | null // What to do when something goes wrong here — shut-offs, panel; null until the office writes it
emergencyMedia: Array<{
id: string // Unique identifier
url: string // The picture or video itself
mimeType: string // Tells a video apart from a picture at render
}> // Pictures of what the plan refers to, oldest first
}>
curl -X GET '{{BASE_URL}}/talent.myLocations' \
-H 'Content-Type: application/json'
The requesting talent's own bookings on one day, each with its client display, services, payments and owed/paid balance
{
date: any // The day to list
}
Array<{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientName: string // Client's name, or their code when they have none (denormalized)
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
confirmed: boolean // The end client has confirmed this booking
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
clientCode: string // The client's short spoken code (D52)
date: string | any // The booking's day (from its schedule)
locationId: string // Deep-link key into the talent's Locations screen
locationName: string
services: Array<{
id: string // Unique identifier
serviceName: string
price: number // The talent's own money for this add-on (D105); $0 when VIP-included
}>
payments: Array<{
id: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
}>
owed: number // fee − discount + captured service fees — the office-side Due (signed, like the tracker); post-D105 extras are her own money and stay out
paid: number // Σ payment.amount against this booking
balance: number // paid − owed
}>
curl -X GET '{{BASE_URL}}/talent.myAppointments?input=%7B%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
The requesting talent's balance carried into a day from her earlier days (LUX-292/D110) — Wrap My Day nets "you owe" against it. Date-grained: it carries into the whole date, without the admin tracker's per-sibling and keyed-line netting (no keyed lines exist after D110; legacy days may read differently here than there)
{
date: any // The day to list
}
{
amount: number
from: string | any[]
}
curl -X GET '{{BASE_URL}}/talent.myCarriedCredit?input=%7B%22date%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
One of the requesting talent's own bookings — NOT_FOUND when it isn't theirs
{
appointmentId: string // UUID identifier
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientName: string // Client's name, or their code when they have none (denormalized)
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
confirmed: boolean // The end client has confirmed this booking
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
clientCode: string // The client's short spoken code (D52)
date: string | any // The booking's day (from its schedule)
locationId: string // Deep-link key into the talent's Locations screen
locationName: string
services: Array<{
id: string // Unique identifier
serviceName: string
price: number // The talent's own money for this add-on (D105); $0 when VIP-included
}>
payments: Array<{
id: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
}>
owed: number // fee − discount + captured service fees — the office-side Due (signed, like the tracker); post-D105 extras are her own money and stay out
paid: number // Σ payment.amount against this booking
balance: number // paid − owed
}
curl -X GET '{{BASE_URL}}/talent.myAppointment?input=%7B%22appointmentId%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
The requesting talent's own pay period (week/month/year): services/extras/cancellation totals and the per-booking breakdown
{
date: any // Any day of the wanted pay period — resolved to its calendar bounds
period: "week" | "month" | "year" // The period grain containing `date`
}
{
startDate: string | any // Date in YYYY-MM-DD format
endDate: string | any // Date in YYYY-MM-DD format
services: number // Σ client-paid package money over live bookings — menu catalog price, or recorded payments minus extras when imported (her side, not the agency fee)
extras: number // Σ the talent's own add-on money over live bookings (D105 — captured fee, else catalog price, VIP inclusions $0)
cancellations: number // Σ talent half of kept cancellation fees (D87/D95)
fees: number // What she hands the agency: Σ (fee − discount) over live bookings + the resolved book-on per venue-day worked (D64/D83 — stored snapshot, else the category default) — signed, since a discount may exceed its fee
total: number // Her net: services + extras + cancellations − fees
rows: Array<{
appointmentId: string // The booking behind this row — a cancellation-fee row carries the cancelled booking
date: string | any // Date in YYYY-MM-DD format
clientCode: string
clientName: string
service: string // Menu package label, or "Cancellation fee" for a kept deposit
locationName: string
methods: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"[] // Distinct payment methods recorded on the booking
total: number // What the booking brought in client-side (LUX-225 flip): catalog price + charged extras, or recorded payments when imported; a cancellation-fee row stays her half of the kept deposit
startTime: string | null // HH:MM booking start — null on a cancellation-fee row
wrappedUpAt: string | any | null // When she closed the booking out (LUX-262) — null while it still needs a wrap-up, so its money isn't final; also null on a cancellation-fee row, which never wraps
}> // Per-booking breakdown, oldest first
}
curl -X GET '{{BASE_URL}}/talent.myPay?input=%7B%22date%22%3A%22value%22%2C%22period%22%3A%22week%22%7D' \
-H 'Content-Type: application/json'
The requesting talent's own priced add-ons, for the wrap-up screen's chips
Array<{
menuServiceId: string // Unique identifier
name: string
price: number
}>
curl -X GET '{{BASE_URL}}/talent.myExtras' \
-H 'Content-Type: application/json'
Close out one of the requesting talent's own bookings: what she actually provided, the add-ons she sold and a private note for the office. Same-day or past only, once per booking, and never a pay change — a reported difference is flagged, not recalculated
{
appointmentId: string // UUID identifier
today: any // The talent's own current calendar date
noShow: boolean // The client never showed — the call is closed out with nothing provided
extraMinutes: 0 | 15 | 30 // Minutes over the booked duration: 0 = as booked
extras: string[] // menuServiceIds she reported selling — from her own catalog; the server prices them
notes?: string // Her private note for the office
cashReceived: number | null // Whole dollars the client handed her; null = not reported
}
{
id: string // Unique identifier
scheduleId: string // Unique identifier
startTime: string | null // HH:MM, or null when unset
clientName: string // Client's name, or their code when they have none (denormalized)
menuLabel: string | null // Menu package label, e.g. "60 min VIP" (denormalized). An imported row has no menu, so it falls back to the source line the call was written as ("HH PSE"); null only when the source named nothing either
fee: number // Talent's fee owed — captured from Menu.fee, or the sheet's historical total on an import (D67 exception)
discount: number // Reduces what is owed; defaults to 0
confirmed: boolean // The end client has confirmed this booking
notes?: string | null // Free-form appointment notes (distinct from the client notes)
cancelledAt: string | any | null // Set when the booking was cancelled and kept as a cancellation fee (D95); null = live. A cancelled booking drops out of Due and booker pay and shows only as a note.
bookerName: string | null // Booker display name (denormalized); null when imported/unresolved
isIndy: boolean // An indy call (LUX-239) — the talent booked it themselves. A normal call everywhere except booker pay: bookerPay is stamped 0 and the payroll rollup skips it
wrappedUpAt: string | any | null // When the talent finalized her wrap-up; null = still open
wrapUpNoShow: boolean // She reported the client never showed for this booking
wrapUpExtraMinutes: number // Reported minutes over the booked duration: 0, 15 or 30
wrapUpNotes: string | null // Her private wrap-up note for the office (distinct from the booking notes)
wrapUpExtras: Array<{
menuServiceId: string // Unique identifier
name: string // Service name as it read at report time
price: number // Catalog price at report time
}> // Add-ons she reported selling, priced as at report time
wrapUpCashReceived: number | null // Whole dollars the client handed her (LUX-224); null = not reported
clientCode: string // The client's short spoken code (D52)
date: string | any // The booking's day (from its schedule)
locationId: string // Deep-link key into the talent's Locations screen
locationName: string
services: Array<{
id: string // Unique identifier
serviceName: string
price: number // The talent's own money for this add-on (D105); $0 when VIP-included
}>
payments: Array<{
id: string // Unique identifier
method: "EMT" | "AmazonGiftCard" | "Cash" | "Credit" | "TalentCredit" | "HotelShare"
amount: number // Whole dollars collected
isDeposit: boolean // A recorded deposit; the balance is settled later
}>
owed: number // fee − discount + captured service fees — the office-side Due (signed, like the tracker); post-D105 extras are her own money and stay out
paid: number // Σ payment.amount against this booking
balance: number // paid − owed
}
curl -X POST '{{BASE_URL}}/talent.wrapUpAppointment' \
-H 'Content-Type: application/json' \
-d '{"appointmentId":"string","today":"value","noShow":true,"extraMinutes":0,"extras":["string"],"cashReceived":0}'
Close out the requesting talent's own scheduled day — she declares the cash envelope handed in. Same-day or past only, once per day, and never a money change: the office sees the flag on the daily tracker
{
scheduleId: string // Her own shift row for the day
today: any // The talent's own current calendar date
}
{
envelopeDroppedAt: string | any // ISO 8601 timestamp
}
curl -X POST '{{BASE_URL}}/talent.dropEnvelope' \
-H 'Content-Type: application/json' \
-d '{"scheduleId":"string","today":"value"}'
The requesting talent's own booking-push switch — on, a phone ping for every booking added to her day; off, the in-app record only. Wrapping up her day turns it off; nothing but this call turns it back on
{
enabled: boolean // Her booking-push switch (LUX-283)
}
any
curl -X POST '{{BASE_URL}}/talent.setBookingPush' \
-H 'Content-Type: application/json' \
-d '{"enabled":true}'
Creates a new talent
{
nickname: string // Unique display name
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
badge?: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId?: string | null // Default phone number ID
phoneNumberStartsAt?: any | null
phoneNumberExpiresAt?: any | null
phoneNumber2Id?: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt?: any | null
phoneNumber2ExpiresAt?: any | null
notes?: string | null // Admin-only forum-ad template
tags?: string[] // Tag IDs
menus?: Array<{
type: "Regular" | "Vip"
durationMinutes: number // Session duration in minutes
price: number // Price in whole dollars
}>
menuServices?: Array<{
serviceId: string // UUID identifier
price?: number | null
}>
images?: Array<{
fileId: string // ID of the uploaded File
variant?: "Original" | "Thumbnail" | "Large"
imageBrand?: "LuxWatermark" | "GgwWatermark" | "VipKittyWatermark" | "Clean" | null
width?: number | null // Image width in pixels
height?: number | null // Image height in pixels
sortOrder?: number // Display sort order
}>
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
}
curl -X POST '{{BASE_URL}}/talent.create' \
-H 'Content-Type: application/json' \
-d '{"nickname":"string"}'
Updates an existing talent
{
nickname?: string // Unique display name
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
badge?: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId?: string | null // Default phone number ID
phoneNumberStartsAt?: any | null
phoneNumberExpiresAt?: any | null
phoneNumber2Id?: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt?: any | null
phoneNumber2ExpiresAt?: any | null
notes?: string | null // Admin-only forum-ad template
tags?: string[] // Tag IDs
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
}
curl -X POST '{{BASE_URL}}/talent.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Deletes a talent
{
id: string // UUID identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
nickname: string // Unique display name
age: number | null // Age in years
height: string | null // Height
weight: string | null // Weight
measurements: string | null // Body measurements
nationality: string | null // Nationality
hair: string | null // Hair color/style
eyes: string | null // Eye color
tattoos: boolean | null // Has tattoos
enhanced: boolean | null // Has cosmetic enhancements
cupSize: string | null // Cup size (VIPKitty-only)
duos: boolean | null // Available for couples/duos (VIPKitty-only)
tagline: string | null // Short promotional headline
bio: string | null // Full bio text
badge: "Visiting" | "Star" | "Vip" | "New" | null
defaultPhoneNumberId: string | null // Default phone number ID
phoneNumberStartsAt: string | any | null // Phone number assignment start (slot 1)
phoneNumberExpiresAt: string | any | null // Phone number expiration date
phoneNumber2Id: string | null // Phone number ID (slot 2)
phoneNumber2StartsAt: string | any | null // Phone number assignment start (slot 2)
phoneNumber2ExpiresAt: string | any | null // Phone number expiration date (slot 2)
}
curl -X POST '{{BASE_URL}}/talent.delete' \
-H 'Content-Type: application/json' \
-d '{"id":"string"}'
Asks the office for a schedule change, a tour in one of the company cities, supplies for one of her own venues, an edit to her own profile or anything else. A request is a ticket — it never edits the schedule or the profile
{
type: "scheduleChange" | "profileChange" | "tour" | "supplies" | "other"
startDate?: "" | string
endDate?: "" | string
cityId?: string | null
locationId?: string | null
note?: string | null
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "timeOff" | "scheduleChange" | "profileChange" | "profileEdit" | "tour" | "supplies" | "other" | "refund"
status: "open" | "approved" | "declined"
talentId: string | null // Null once the talent is deleted
talentName: string | null // Snapshot taken when the request was sent; null on a refund row (LUX-310)
startDate: string | any | null
endDate: string | any | null
cityId: string | null // Null unless the ask was a tour
cityName: string | null // Snapshot taken when the request was sent
locationId: string | null // Null unless the ask was for supplies
locationName: string | null // Snapshot taken when the request was sent
note: string | null
payload: {
from: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // Her record when she asked
to: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // What she is asking it to become
} | null // The proposed edit on a profileEdit; null on every other type
clientId: string | null // Null once the client is deleted
clientName: string | null // Snapshot taken when the refund was requested
appointmentId: string | null // The cancelled booking the money was recorded on
refundAmount: number | null
refundEmail: string | null
refundPaidAt: string | any | null // When the money was sent back; null while unpaid
decisionNote: string | null
decidedAt: string | any | null // Null while open
}
curl -X POST '{{BASE_URL}}/talent.createRequest' \
-H 'Content-Type: application/json' \
-d '{"type":"scheduleChange"}'
Asks the office to change the requesting talent's own safe profile fields. Nothing is written here: the proposed change rides on a change request, and only an approval applies it. The schema's picked allowlist is the whole proposable surface — admin-owned fields (nickname, badge, notes, fees, phone assignment) are not in it, and phone stays in the SMS-verify flow. Identity comes off the session's talent link, never input
{
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "timeOff" | "scheduleChange" | "profileChange" | "profileEdit" | "tour" | "supplies" | "other" | "refund"
status: "open" | "approved" | "declined"
talentId: string | null // Null once the talent is deleted
talentName: string | null // Snapshot taken when the request was sent; null on a refund row (LUX-310)
startDate: string | any | null
endDate: string | any | null
cityId: string | null // Null unless the ask was a tour
cityName: string | null // Snapshot taken when the request was sent
locationId: string | null // Null unless the ask was for supplies
locationName: string | null // Snapshot taken when the request was sent
note: string | null
payload: {
from: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // Her record when she asked
to: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // What she is asking it to become
} | null // The proposed edit on a profileEdit; null on every other type
clientId: string | null // Null once the client is deleted
clientName: string | null // Snapshot taken when the refund was requested
appointmentId: string | null // The cancelled booking the money was recorded on
refundAmount: number | null
refundEmail: string | null
refundPaidAt: string | any | null // When the money was sent back; null while unpaid
decisionNote: string | null
decidedAt: string | any | null // Null while open
}
curl -X POST '{{BASE_URL}}/talent.requestProfileEdit' \
-H 'Content-Type: application/json' \
-d '{}'
The requesting talent's own change requests, newest first, each with the office's answer once decided
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "timeOff" | "scheduleChange" | "profileChange" | "profileEdit" | "tour" | "supplies" | "other" | "refund"
status: "open" | "approved" | "declined"
talentId: string | null // Null once the talent is deleted
talentName: string | null // Snapshot taken when the request was sent; null on a refund row (LUX-310)
startDate: string | any | null
endDate: string | any | null
cityId: string | null // Null unless the ask was a tour
cityName: string | null // Snapshot taken when the request was sent
locationId: string | null // Null unless the ask was for supplies
locationName: string | null // Snapshot taken when the request was sent
note: string | null
payload: {
from: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // Her record when she asked
to: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // What she is asking it to become
} | null // The proposed edit on a profileEdit; null on every other type
clientId: string | null // Null once the client is deleted
clientName: string | null // Snapshot taken when the refund was requested
appointmentId: string | null // The cancelled booking the money was recorded on
refundAmount: number | null
refundEmail: string | null
refundPaidAt: string | any | null // When the money was sent back; null while unpaid
decisionNote: string | null
decidedAt: string | any | null // Null while open
}>
curl -X GET '{{BASE_URL}}/talent.myRequests' \
-H 'Content-Type: application/json'
The days a tour ask would land over the city's capacity (LUX-271) — advisory only, the form shows it as a warning and the request always goes through
{
cityId: string // UUID identifier
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
days: Array<{
date: string | any // Date in YYYY-MM-DD format
scheduled: number // Talent already scheduled in the city that day
capacity: number // The city's summed daily capacity
}> // Only the over-capacity days, earliest first; empty = all clear
}
curl -X GET '{{BASE_URL}}/talent.capacityPreview?input=%7B%22cityId%22%3A%22string%22%2C%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'
Change requests from talent for the requesting company, newest first, optionally filtered by status
{
status?: "open" | "approved" | "declined"
}
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "timeOff" | "scheduleChange" | "profileChange" | "profileEdit" | "tour" | "supplies" | "other" | "refund"
status: "open" | "approved" | "declined"
talentId: string | null // Null once the talent is deleted
talentName: string | null // Snapshot taken when the request was sent; null on a refund row (LUX-310)
startDate: string | any | null
endDate: string | any | null
cityId: string | null // Null unless the ask was a tour
cityName: string | null // Snapshot taken when the request was sent
locationId: string | null // Null unless the ask was for supplies
locationName: string | null // Snapshot taken when the request was sent
note: string | null
payload: {
from: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // Her record when she asked
to: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // What she is asking it to become
} | null // The proposed edit on a profileEdit; null on every other type
clientId: string | null // Null once the client is deleted
clientName: string | null // Snapshot taken when the refund was requested
appointmentId: string | null // The cancelled booking the money was recorded on
refundAmount: number | null
refundEmail: string | null
refundPaidAt: string | any | null // When the money was sent back; null while unpaid
decisionNote: string | null
decidedAt: string | any | null // Null while open
}>
curl -X GET '{{BASE_URL}}/talent.requestQueue?input=%7B%7D' \
-H 'Content-Type: application/json'
Approves or declines an open change request with an optional note. Approving does not touch the schedule — the office applies that by hand. The exception is a profile edit, which carries the change itself: approving writes it to her profile in the same transaction, and declining leaves the profile untouched
{
id: string // UUID identifier
status: "approved" | "declined"
decisionNote?: string
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
type: "timeOff" | "scheduleChange" | "profileChange" | "profileEdit" | "tour" | "supplies" | "other" | "refund"
status: "open" | "approved" | "declined"
talentId: string | null // Null once the talent is deleted
talentName: string | null // Snapshot taken when the request was sent; null on a refund row (LUX-310)
startDate: string | any | null
endDate: string | any | null
cityId: string | null // Null unless the ask was a tour
cityName: string | null // Snapshot taken when the request was sent
locationId: string | null // Null unless the ask was for supplies
locationName: string | null // Snapshot taken when the request was sent
note: string | null
payload: {
from: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // Her record when she asked
to: {
age?: number | null // Age in years
height?: string | null // Height
weight?: string | null // Weight
measurements?: string | null // Body measurements
nationality?: string | null // Nationality
hair?: string | null // Hair color/style
eyes?: string | null // Eye color
tattoos?: boolean | null // Has tattoos
enhanced?: boolean | null // Has cosmetic enhancements
cupSize?: string | null // Cup size (VIPKitty-only)
duos?: boolean | null // Available for couples/duos (VIPKitty-only)
tagline?: string | null // Short promotional headline
bio?: string | null // Full bio text
} // What she is asking it to become
} | null // The proposed edit on a profileEdit; null on every other type
clientId: string | null // Null once the client is deleted
clientName: string | null // Snapshot taken when the refund was requested
appointmentId: string | null // The cancelled booking the money was recorded on
refundAmount: number | null
refundEmail: string | null
refundPaidAt: string | any | null // When the money was sent back; null while unpaid
decisionNote: string | null
decidedAt: string | any | null // Null while open
}
curl -X POST '{{BASE_URL}}/talent.decideRequest' \
-H 'Content-Type: application/json' \
-d '{"id":"string","status":"approved"}'
User
Returns the authenticated user profile
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
talentId?: string | null // Linked talent id — the performer this account acts as
seenTours: string[] // Onboarding tour ids this user has completed or dismissed
}
curl -X GET '{{BASE_URL}}/user.me' \
-H 'Content-Type: application/json'
Updates the authenticated user profile (name, email) — the settings page. Role and phone are out of reach: role changes are admin-only, phone changes go through the SMS-verify flow.
{
email?: string // Email address
name?: string // User's display name
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
talentId?: string | null // Linked talent id — the performer this account acts as
seenTours: string[] // Onboarding tour ids this user has completed or dismissed
}
curl -X POST '{{BASE_URL}}/user.updateSelf' \
-H 'Content-Type: application/json' \
-d '{}'
Records an onboarding tour as seen so it never auto-starts again. A no-op while impersonating — standing in as someone must not mark their tours.
{
tourId: "welcome-admin" | "welcome-booker" | "notifications" | "complete-profile" // Tour registry id
}
any
curl -X POST '{{BASE_URL}}/user.markTourSeen' \
-H 'Content-Type: application/json' \
-d '{"tourId":"welcome-admin"}'
Creates a new user via the auth admin API
{
email: string // Email address
name: string // User's display name
password: string // Account password (min 8 characters)
role: "user" | "admin" | "booker" | "talent" | "support" // User role — booker marks staff who take bookings, talent performs, support keeps the locations running
payRate?: number | null // Whole dollars per booked appointment; bookers only (D76)
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
talentId?: string | null // Linked talent id — the performer this account acts as
seenTours: string[] // Onboarding tour ids this user has completed or dismissed
}
curl -X POST '{{BASE_URL}}/user.create' \
-H 'Content-Type: application/json' \
-d '{"email":"string","name":"string","password":"string","role":"user"}'
Returns a user by their ID
{
id: string // Unique identifier
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
talentId?: string | null // Linked talent id — the performer this account acts as
seenTours: string[] // Onboarding tour ids this user has completed or dismissed
}
curl -X GET '{{BASE_URL}}/user.getById?input=%7B%22id%22%3A%22string%22%7D' \
-H 'Content-Type: application/json'
Updates an existing user
{
id: string // Unique identifier
data: {
email?: string // Email address
name?: string // User's display name
role?: "user" | "admin" | "booker" | "talent" | "support" // User role — booker marks staff who take bookings, talent performs, support keeps the locations running
phoneNumber?: any | null
talentId?: string | null // Linked talent id — the performer this account acts as
}
}
{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
talentId?: string | null // Linked talent id — the performer this account acts as
seenTours: string[] // Onboarding tour ids this user has completed or dismissed
}
curl -X POST '{{BASE_URL}}/user.update' \
-H 'Content-Type: application/json' \
-d '{"id":"string","data":{}}'
Sets a new password for a user via the auth admin API
{
userId: string // Unique identifier
newPassword: string
}
curl -X POST '{{BASE_URL}}/user.setPassword' \
-H 'Content-Type: application/json' \
-d '{"userId":"string","newPassword":"string"}'
Folds one user into another and deletes it: bookings, sent texts, payroll rows and uploads move to the survivor; overlapping weekly-payment receipts are summed. The retired login (sessions, passkeys) is destroyed. Irreversible.
{
keepUserId: string // The user that survives
mergeUserId: string // The user that is retired
}
{
keptName: string // The user that survived
retiredName: string // The user that no longer exists
appointmentsMoved: number // Bookings re-pointed to the survivor
}
curl -X POST '{{BASE_URL}}/user.merge' \
-H 'Content-Type: application/json' \
-d '{"keepUserId":"string","mergeUserId":"string"}'
Returns all users
Array<{
id: string // Unique identifier
createdAt: string | any // Record creation timestamp
updatedAt: string | any // Record last-update timestamp
email: string // Email address
name: string | null
role: string | null // User role — admin, booker, talent, support or user
phoneNumber?: string | null // Login phone number, E.164
banned?: boolean | null // Deactivated — no login, no pay
}>
curl -X GET '{{BASE_URL}}/user.list' \
-H 'Content-Type: application/json'
WeeklyReport
Collected cash (by rail, per city), booker pay, and expenses (by category) rolled up over a week — the Mon–Sun bounds are sent by the client
{
startDate: any // YYYY-MM-DD string parsed to UTC midnight Date
endDate: any // YYYY-MM-DD string parsed to UTC midnight Date
}
{
weekStart: string | any // Date in YYYY-MM-DD format
weekEnd: string | any // Date in YYYY-MM-DD format
moneyIn: {
cash: number
emt: number
giftCard: number
credit: number
total: number
}
bookerPay: number
cancellationAgency: number
talentCredit: number
expensesByCategory: Record<string, number>
totalExpenses: number
generalExpenses: number
creditPaidOut: number
totalOut: number
net: number // Whole dollars; negative in a loss week
cities: Array<{
city: string
moneyIn: {
cash: number
emt: number
giftCard: number
credit: number
total: number
}
bookerPay: number
cancellationAgency: number
talentCredit: number
expenses: number
}>
}
curl -X GET '{{BASE_URL}}/weeklyReport.byWeek?input=%7B%22startDate%22%3A%22value%22%2C%22endDate%22%3A%22value%22%7D' \
-H 'Content-Type: application/json'