Skip to main content

Tatiya Platform API (1.0.0-draft)

Download OpenAPI specification:Download

Build booking, membership and community applications on top of a Tatiya space.

The Tatiya Platform API lets an application read a space's public offering, take bookings on its behalf, and react to what happens inside it.

Two surfaces

Public (/v1/public/...) needs no credential. It serves exactly what a visitor to the space's own website can already see: profile, booking types, availability, published posts. Cacheable, and safe to call from a browser.

Authenticated (everything else) needs an API key. A key belongs to one space and carries a fixed set of scopes, so the space is never a parameter — the key decides which tenant a call reaches.

What is not here

No cross-space endpoints. A key that could name a tenant is a key that can name the wrong one; an integration spanning several spaces holds several keys. OAuth for "an app acting on behalf of any host" is a later phase.

Public

Unauthenticated reads of a space's published offering.

Read a space's public profile

path Parameters
spaceHandle
required
string^[a-z0-9][a-z0-9-]{1,62}$
Example: sunrise

The space's handle — the sunrise in sunrise.tatiya.space.

query Parameters
locale
string
Default: "en"
Example: locale=th

BCP-47 tag. Exact match — content absent in this locale is absent, not substituted from another.

Responses

Response samples

Content type
application/json
{
  • "handle": "sunrise",
  • "name": "Sunrise Studio",
  • "tagline": "string",
  • "about": "string",
  • "timezone": "Asia/Bangkok",
  • "currency": "THB",
  • "locales": [
    ],
  • "website_url": "http://example.com",
  • "logo_url": "http://example.com",
  • "location": {
    },
  • "open_hours": [
    ],
  • "social_links": [],
  • "accepts_bookings": true
}

List the space's bookable session types

path Parameters
spaceHandle
required
string^[a-z0-9][a-z0-9-]{1,62}$
Example: sunrise

The space's handle — the sunrise in sunrise.tatiya.space.

query Parameters
locale
string
Default: "en"
Example: locale=th

BCP-47 tag. Exact match — content absent in this locale is absent, not substituted from another.

limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Compute bookable slots for a date range

Wraps computeSlots (@tatiya/shared/availability), which today has no HTTP surface at all. Times are wall-clock in the space's own timezone; the zone is returned alongside so a client never has to guess it.

A slot's presence means the window is open and unbooked at compute time. It is not a hold — the authoritative capacity check happens inside the booking transaction, and a slot can fill between this call and that one.

path Parameters
spaceHandle
required
string^[a-z0-9][a-z0-9-]{1,62}$
Example: sunrise

The space's handle — the sunrise in sunrise.tatiya.space.

bookingTypeId
required
string <uuid>
query Parameters
from
required
string <date>

Inclusive start date, YYYY-MM-DD, in the space's timezone.

to
required
string <date>

Inclusive end date. At most 62 days after from.

Responses

Response samples

Content type
application/json
{
  • "timezone": "Asia/Bangkok",
  • "slots": [
    ]
}

Book a session as a customer

The customer-facing write path. Distinct from POST /bookings, which is the host acting on someone's behalf and can carry policy overrides — this one cannot, by construction.

Identity resolves the way the rest of the platform resolves a guest: email lowercased, then digits-only phone. A returning customer is recognised without an account; someone who gives neither is a first-timer every visit.

Requires the space to have opted this integration in. An unauthenticated caller may create a booking only when the space allows public booking; otherwise present an API key with bookings:write.

Authorizations:
NoneApiKeyAuth
path Parameters
spaceHandle
required
string^[a-z0-9][a-z0-9-]{1,62}$
Example: sunrise

The space's handle — the sunrise in sunrise.tatiya.space.

header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
booking_type_id
required
string <uuid>
scheduled_date
required
string <date>
scheduled_start_time
required
string
required
object
source
string
Default: "external_partner"
Enum: "native_app" "discovery_app" "classpass" "external_partner" "admin_walk_in" "admin_line" "admin_whatsapp" "admin_messenger" "admin_phone" "admin_instagram" "admin_other" "website_enquiry"

Where the booking came from. external_partner and discovery_app already exist in the platform's own enum and are the values a third-party integration should send.

marketing_consent
boolean
Default: false

Whether the customer agreed to marketing contact at the point of booking. Recorded with its own timestamp and the consent text shown. Never default this to true.

notes
string <= 1000 characters

Responses

Request samples

Content type
application/json
{
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "customer": {},
  • "source": "external_partner",
  • "marketing_consent": false,
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

List published posts

Locale is an exact match, not a negotiated fallback — a post absent in the requested locale is absent. Post slugs are per-locale, so a URL built for one locale is not valid in another.

path Parameters
spaceHandle
required
string^[a-z0-9][a-z0-9-]{1,62}$
Example: sunrise

The space's handle — the sunrise in sunrise.tatiya.space.

query Parameters
locale
string
Default: "en"
Example: locale=th

BCP-47 tag. Exact match — content absent in this locale is absent, not substituted from another.

limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Bookings

Read and write bookings for the key's space.

List bookings

Scoped to the key's space. Always paginated — there is no unbounded form, deliberately: the tRPC procedure this replaces reads the whole table twice per call and that is not a contract worth publishing.

Authorizations:
ApiKeyAuth
query Parameters
status
string (BookingStatus)
Enum: "pending" "confirmed" "paid" "completed" "cancelled" "no_show"
source
string (BookingSource)
Enum: "native_app" "discovery_app" "classpass" "external_partner" "admin_walk_in" "admin_line" "admin_whatsapp" "admin_messenger" "admin_phone" "admin_instagram" "admin_other" "website_enquiry"

Where the booking came from. external_partner and discovery_app already exist in the platform's own enum and are the values a third-party integration should send.

from
string <date>

Inclusive lower bound on scheduled_date.

to
string <date>
awaiting_payment
boolean

Priced, unpaid, and neither cancelled nor no-showed.

limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Create a booking on a customer's behalf

The host-side write path — a booking taken over LINE, WhatsApp, the phone, or through a partner's own checkout. Requires bookings:write.

allow_over_capacity and allow_policy_bypass are deliberate overrides, not conveniences: send them only after a human has been told what they are overriding. They are refused unless the key carries bookings:override.

Authorizations:
ApiKeyAuth
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
booking_type_id
required
string <uuid>
scheduled_date
required
string <date>
scheduled_start_time
required
string
required
object
source
string
Default: "external_partner"
Enum: "native_app" "discovery_app" "classpass" "external_partner" "admin_walk_in" "admin_line" "admin_whatsapp" "admin_messenger" "admin_phone" "admin_instagram" "admin_other" "website_enquiry"

Where the booking came from. external_partner and discovery_app already exist in the platform's own enum and are the values a third-party integration should send.

marketing_consent
boolean
Default: false

Whether the customer agreed to marketing contact at the point of booking. Recorded with its own timestamp and the consent text shown. Never default this to true.

notes
string <= 1000 characters
payment_status
string
Default: "pay_later"
Enum: "paid" "pay_later" "free"
check_in_now
boolean
Default: false
allow_over_capacity
boolean
Default: false

Requires bookings:override. Refused otherwise.

allow_policy_bypass
boolean
Default: false

Requires bookings:override. Refused otherwise.

Responses

Request samples

Content type
application/json
{
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "customer": {},
  • "source": "external_partner",
  • "marketing_consent": false,
  • "notes": "string",
  • "payment_status": "paid",
  • "check_in_now": false,
  • "allow_over_capacity": false,
  • "allow_policy_bypass": false
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Read one booking

Authorizations:
ApiKeyAuth
path Parameters
bookingId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Update a booking's notes or source

Status is not settable here. A booking's lifecycle moves through the named transitions below, each of which publishes its own domain event — a generic status write would let a caller skip the event and leave the analytics, notification and community subscribers unfired.

Authorizations:
ApiKeyAuth
path Parameters
bookingId
required
string <uuid>
Request Body schema: application/json
required
non-empty
notes
string or null <= 1000 characters
source
string (BookingSource)
Enum: "native_app" "discovery_app" "classpass" "external_partner" "admin_walk_in" "admin_line" "admin_whatsapp" "admin_messenger" "admin_phone" "admin_instagram" "admin_other" "website_enquiry"

Where the booking came from. external_partner and discovery_app already exist in the platform's own enum and are the values a third-party integration should send.

Responses

Request samples

Content type
application/json
{
  • "notes": "string",
  • "source": "native_app"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Cancel a booking

Publishes booking.cancelled. A cancelled booking still counts against an intro offer — forgiving them would make cancel-and-rebook an unlimited free-session loop.

Authorizations:
ApiKeyAuth
path Parameters
bookingId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
reason
string <= 500 characters

Responses

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Check a customer in

Requires the space's tier to include attendance tracking; a Free-tier space gets 403 feature_not_available. Publishes booking.checked_in.

Authorizations:
ApiKeyAuth
path Parameters
bookingId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Record a payment against a booking

Publishes booking.paid. Requires bookings:write.

Authorizations:
ApiKeyAuth
path Parameters
bookingId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
method
required
string
Enum: "cash" "bank_transfer" "card" "promptpay" "other"
amount_minor
integer >= 0

Minor units. Omit to record the booking's own price.

reference
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "method": "cash",
  • "amount_minor": 0,
  • "reference": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "booking_type_id": "7012cefa-e2da-4a08-9700-305b62e5cdd2",
  • "booking_type_title": "string",
  • "scheduled_date": "2019-08-24",
  • "scheduled_start_time": "07:00",
  • "scheduled_end_time": "string",
  • "status": "pending",
  • "source": "native_app",
  • "customer": {
    },
  • "price": {
    },
  • "paid_at": "string",
  • "checked_in_at": "string",
  • "cancelled_at": "string",
  • "was_intro_offer": true,
  • "covered_by_membership": {
    },
  • "notes": "string",
  • "created_at": "string"
}

Booking types

The sessions, classes and appointments a space offers.

List booking types

Authorizations:
ApiKeyAuth
query Parameters
include_archived
boolean
Default: false
limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Read one booking type

Authorizations:
ApiKeyAuth
path Parameters
bookingTypeId
required
string <uuid>
query Parameters
member_id
string <uuid>

Ask whether this member still has the booking type's intro offer, returned as intro_offer.applies_to_member. Omit it and that field is null — "not asked", which is NOT the same as false. Requires members:read IN ADDITION to booking_types:read, because the answer is a readout of one person's booking history at this space rather than a property of the offering. A key without it gets 403, not a null answer. Advisory: createBooking re-resolves eligibility inside its transaction under an advisory lock, and that is the only verdict that can price a booking.

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "slug": "string",
  • "title": "Vinyasa Flow",
  • "description": "string",
  • "duration_minutes": 60,
  • "price_amount": 0,
  • "capacity": 0,
  • "categories": [
    ],
  • "intro_offer": {
    },
  • "archived": true,
  • "created_at": "string",
  • "updated_at": "string"
}

Members

The space's roster and each member's history.

List the space's roster

Requires members:read. Never returns a credential field of any kind.

Authorizations:
ApiKeyAuth
query Parameters
lifecycle_stage
string
Enum: "lead" "first_timer" "returning" "regular" "at_risk" "lapsed"
limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Add a member by email

Requires members:write. Always creates a plain roster entry — the same identity resolution the admin UI's own "add member" flow uses. 409s if the email is already registered anywhere on the platform, not only in this space.

Authorizations:
ApiKeyAuth
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
email
required
string <email>
name
string <= 200 characters

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "email": "string",
  • "phone": "string",
  • "joined_at": "string",
  • "lifecycle_stage": "string",
  • "last_visit_at": "string",
  • "total_bookings": 0,
  • "consent": {
    },
  • "recent_visits": [
    ],
  • "active_memberships": [
    ],
  • "roles": [
    ]
}

Read one member

Authorizations:
ApiKeyAuth
path Parameters
memberId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "email": "string",
  • "phone": "string",
  • "joined_at": "string",
  • "lifecycle_stage": "string",
  • "last_visit_at": "string",
  • "total_bookings": 0,
  • "consent": {
    },
  • "recent_visits": [
    ],
  • "active_memberships": [
    ],
  • "roles": [
    ]
}

Update one role's profile fields

Requires members:write. role is required, not inferred — a member can hold several roles (participant AND facilitator, say), each with its own bio/progression/certifications/specialties, so there is no single "the" profile to patch without naming which role's row it is.

Authorizations:
ApiKeyAuth
path Parameters
memberId
required
string <uuid>
Request Body schema: application/json
required
role
required
string
Enum: "participant" "facilitator" "guide" "artist" "host"
bio
string
progression_level
string
certifications
Array of strings
specialties
Array of strings

Responses

Request samples

Content type
application/json
{
  • "role": "participant",
  • "bio": "string",
  • "progression_level": "string",
  • "certifications": [
    ],
  • "specialties": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "name": "string",
  • "email": "string",
  • "phone": "string",
  • "joined_at": "string",
  • "lifecycle_stage": "string",
  • "last_visit_at": "string",
  • "total_bookings": 0,
  • "consent": {
    },
  • "recent_visits": [
    ],
  • "active_memberships": [
    ],
  • "roles": [
    ]
}

Memberships

Plans and the memberships held against them.

List membership plans

Authorizations:
ApiKeyAuth
query Parameters
limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

List memberships

Authorizations:
ApiKeyAuth
query Parameters
status
string
Enum: "active" "cancelled" "expired" "paused"
member_id
string <uuid>
limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Assign a plan to a member

Requires memberships:write. Refuses a plan that is inactive, archived, or belongs to a different space than the key's.

Authorizations:
ApiKeyAuth
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
member_id
required
string <uuid>
plan_id
required
string <uuid>
start_date
string <date>

Defaults to today.

notes
string <= 2000 characters

Responses

Request samples

Content type
application/json
{
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "start_date": "2019-08-24",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Update a membership's notes

Authorizations:
ApiKeyAuth
path Parameters
membershipId
required
string <uuid>
Request Body schema: application/json
required
notes
required
string <= 2000 characters

Responses

Request samples

Content type
application/json
{
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Cancel a membership

Publishes membership.cancelled. Requires memberships:write.

Authorizations:
ApiKeyAuth
path Parameters
membershipId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
reason
string <= 1000 characters
immediate
boolean
Default: false

End access now instead of at the current period's end.

Responses

Request samples

Content type
application/json
{
  • "reason": "string",
  • "immediate": false
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Pause a membership

Only an active membership can be paused. Publishes membership.paused. Requires memberships:write.

Authorizations:
ApiKeyAuth
path Parameters
membershipId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Resume a paused membership

Only a paused membership can be resumed. Publishes membership.resumed. Requires memberships:write.

Authorizations:
ApiKeyAuth
path Parameters
membershipId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Record a payment against a membership

Publishes membership.paid, and reactivates an expired membership. Requires memberships:write. method is narrower than a booking payment's — promptpay, bank_transfer, cash or other only.

Authorizations:
ApiKeyAuth
path Parameters
membershipId
required
string <uuid>
header Parameters
Idempotency-Key
string <= 255 characters

A client-generated key, unique per logical operation. Replaying a request with the same key returns the original response instead of acting twice, for 24 hours. Send one on every write from a client that might retry — which is every client.

Request Body schema: application/json
required
method
required
string
Enum: "promptpay" "bank_transfer" "cash" "other"
amount_minor
required
integer >= 0
reference
string <= 200 characters

Responses

Request samples

Content type
application/json
{
  • "method": "promptpay",
  • "amount_minor": 0,
  • "reference": "string"
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "plan_id": "00713021-9aea-41da-9a88-87760c08fa72",
  • "plan_name": "string",
  • "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
  • "status": "active",
  • "started_at": "string",
  • "current_period_end": "string",
  • "cancelled_at": "string"
}

Webhooks

🚧 Not built yet. Every operation under this tag is a design, not a live route — calling any of them today 404s. See docs/api/README.md's "What exists right now" for the current state, and docs/api/README.md's "Webhooks" section for the design itself.

🚧 Not built yet — List registered endpoints

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

🚧 Not built yet — Register an endpoint

🚧 Not built yet — this operation does not exist in apps/api today; calling it 404s. Documented here as the design.

Once shipped: requires the space's tier to include webhooks. The signing secret is in this response and nowhere else — the same rule the agent grant follows. Lose it and you rotate rather than look it up.

Authorizations:
ApiKeyAuth
Request Body schema: application/json
required
url
required
string <uri>

Must be https. Redirects are not followed.

event_types
required
Array of strings (EventType) non-empty
Items Enum: "booking.created" "booking.confirmed" "booking.paid" "booking.cancelled" "booking.checked_in" "booking.completed" "booking.no_show" "membership.assigned" "membership.paid" "membership.cancelled" "membership.paused" "membership.resumed"
description
string <= 200 characters

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "event_types": [
    ],
  • "description": "string",
  • "status": "active",
  • "created_at": "2019-08-24T14:15:22Z",
  • "signing_secret": "string"
}

🚧 Not built yet — Delete an endpoint

Authorizations:
ApiKeyAuth
path Parameters
endpointId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

🚧 Not built yet — Inspect delivery attempts

The last 30 days. This is the debugging surface — without it, a failing integration is a black box.

Authorizations:
ApiKeyAuth
query Parameters
endpoint_id
string <uuid>
status
string
Enum: "pending" "succeeded" "failed" "exhausted"
limit
integer [ 1 .. 100 ]
Default: 25
cursor
string

Opaque. Take it from the previous page's next_cursor; never construct one.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

🚧 Not built yet — Replay one delivery

Authorizations:
ApiKeyAuth
path Parameters
deliveryId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Meta

Key introspection and service health.

Describe the calling key

What this key is and what it may do. Call it first — it is how a client discovers its own scopes rather than discovering them from a 403.

Authorizations:
ApiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
  • "label": "string",
  • "space": {
    },
  • "scopes": [
    ],
  • "expires_at": "string"
}

Service health

The real route is unversioned — GET /health, not GET /v1/health. It's documented under this spec's /v1 server for discoverability alongside the rest of the API, but this path segment is the one exception to that base URL: strip /v1 when actually calling it. Static today ({"status":"ok"}, no dependency probe) — apps/api/src/index.ts.

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "database": "ok",
  • "timestamp": "2019-08-24T14:15:22Z"
}

A booking was created Webhook

Authorizations:
ApiKeyAuth
Request Body schema: application/json
id
required
string <uuid>

The domain_events row id. Stable across retries — use it to deduplicate, because delivery is at-least-once.

type
required
string (EventType)
Enum: "booking.created" "booking.confirmed" "booking.paid" "booking.cancelled" "booking.checked_in" "booking.completed" "booking.no_show" "membership.assigned" "membership.paid" "membership.cancelled" "membership.paused" "membership.resumed"
created_at
required
string <date-time>
space_handle
required
string
required
object

The event payload. Carries the facts that cannot be cheaply re-derived; anything else (a space's current name, a booking type's current title) should be read fresh rather than trusted from an event that may be minutes old.

Responses

Request samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "type": "booking.created",
  • "created_at": "2019-08-24T14:15:22Z",
  • "space_handle": "string",
  • "data": { }
}

A booking was cancelled Webhook

Authorizations:
ApiKeyAuth
Request Body schema: application/json
id
required
string <uuid>

The domain_events row id. Stable across retries — use it to deduplicate, because delivery is at-least-once.

type
required
string (EventType)
Enum: "booking.created" "booking.confirmed" "booking.paid" "booking.cancelled" "booking.checked_in" "booking.completed" "booking.no_show" "membership.assigned" "membership.paid" "membership.cancelled" "membership.paused" "membership.resumed"
created_at
required
string <date-time>
space_handle
required
string
required
object

The event payload. Carries the facts that cannot be cheaply re-derived; anything else (a space's current name, a booking type's current title) should be read fresh rather than trusted from an event that may be minutes old.

Responses

Request samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "type": "booking.created",
  • "created_at": "2019-08-24T14:15:22Z",
  • "space_handle": "string",
  • "data": { }
}

A membership was assigned Webhook

Authorizations:
ApiKeyAuth
Request Body schema: application/json
id
required
string <uuid>

The domain_events row id. Stable across retries — use it to deduplicate, because delivery is at-least-once.

type
required
string (EventType)
Enum: "booking.created" "booking.confirmed" "booking.paid" "booking.cancelled" "booking.checked_in" "booking.completed" "booking.no_show" "membership.assigned" "membership.paid" "membership.cancelled" "membership.paused" "membership.resumed"
created_at
required
string <date-time>
space_handle
required
string
required
object

The event payload. Carries the facts that cannot be cheaply re-derived; anything else (a space's current name, a booking type's current title) should be read fresh rather than trusted from an event that may be minutes old.

Responses

Request samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "type": "booking.created",
  • "created_at": "2019-08-24T14:15:22Z",
  • "space_handle": "string",
  • "data": { }
}