Reference

Endpoint reference

Every endpoint in v1, organised by resource. All paths are relative to the base URL https://rest.setora.co.uk/v1.

Locations, services & staff

Read-only resources that describe what a shop sells, where, and who delivers it. These endpoints are the starting point for any booking flow.

GET/locations

List the locations belonging to the authenticated organisation. Returns active locations only.

Scope required · read

Response fields

NameDescription
  • id

    Unique identifier for the location.

  • slug

    URL-safe identifier used in API paths (e.g. /locations/soho/…).

  • name

    Display name of the location.

  • timezone

    IANA timezone identifier. All availability slots for this location are returned in this timezone.

  • street_address

    Street-level address line.

  • city

    City or town.

  • postal_code

    Postal or ZIP code.

  • country

    Two-letter country code.

json200 OK
{
  "data": [
    {
      "id": "4f8d3a91-2b6c-4e7f-9a01-1c5d8e2f6b3a",
      "slug": "soho",
      "name": "Soho",
      "timezone": "Europe/London",
      "street_address": "12 Berwick Street",
      "city": "London",
      "postal_code": "W1F 0PT",
      "country": "GB"
    }
  ]
}
GET/locations/{slug}/services

List the services bookable at a location, including pricing, duration, deposit requirement, and which staff can deliver each service. Pricing and duration may differ from the org-wide default if a per-location override is set.

Scope required · read

Response fields

NameDescription
  • id

    Unique identifier for the service.

  • name

    Display name of the service.

  • duration_minutes

    Length of the service in minutes.

  • price

    Contains amount (integer, minor units) and currency (ISO 4217). May differ from the org-wide default if a per-location override is set.

  • deposit

    Deposit required to confirm a booking. null when no deposit is needed. Contains amount and currency.

  • category

    Service category label set by the shop.

  • online_bookable

    Whether the service is available for online booking. Services with false can only be booked in-person.

  • staff

    Staff members who can deliver this service. Each object contains id (UUID) and name.

json200 OK
{
  "data": [
    {
      "id": "2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15",
      "name": "Haircut",
      "duration_minutes": 30,
      "price": { "amount": 2500, "currency": "GBP" },
      "deposit": { "amount": 500, "currency": "GBP" },   // null when no deposit
      "category": "Hair",
      "online_bookable": true,
      "staff": [
        { "id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84", "name": "Kai" },
        { "id": "c1d4f8a2-6e3b-49d7-a085-2b8c5f1e7d39", "name": "Jordan" }
      ]
    }
  ]
}
GET/locations/{slug}/staff

List active staff at a location. Each staff member returns the services they can deliver, so callers can filter availability accordingly.

Scope required · read

Response fields

NameDescription
  • id

    Unique identifier for the staff member.

  • name

    Display name of the staff member.

  • service_ids

    UUIDs of the services this staff member can deliver. Use these to filter availability.

json200 OK
{
  "data": [
    {
      "id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84",
      "name": "Kai",
      "service_ids": ["2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15", "e8b1d4f2-3a9c-47e5-bd06-4c7f2a1e8b39"]
    }
  ]
}

Availability

Real-time slot lookup. Backed by the same engine as the public booking portal, so deposit rules and lead-time windows are honoured automatically.

GET/locations/{slug}/availability

Return bookable slots for one or more services at a location. Slots are returned in the location's local timezone with a UTC offset.

Scope required · read

NameDescription
  • service_ids

    required

    One or more service IDs. Determines total slot length and which staff are eligible. For multi-service bookings, pass all service IDs.

  • date

    required

    Local date to start the search from, in the location's timezone.

  • days

    Number of days to search forward from date. Defaults to 1.

  • staff_id

    Restrict results to a specific staff member. Omit for any-staff availability.

bashExample request
curl https://rest.setora.co.uk/v1/locations/soho/availability \
  -G \
  --data-urlencode "service_ids=2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15" \
  --data-urlencode "date=2026-05-12" \
  --data-urlencode "days=3" \
  -H "Authorization: Bearer sk_live_..."

Response fields

NameDescription
  • location_slug

    The location these slots belong to.

  • timezone

    IANA timezone of the location. All slot times are expressed in this timezone.

  • service_ids

    The service IDs that were queried.

  • duration

    Total slot duration. Contains minutes (integer) and display (human-readable string).

  • slots

    Available time slots. Each contains starts_at, ends_at (ISO 8601 with UTC offset), and staff_id (UUID of the assigned staff member).

json200 OK
{
  "data": {
    "location_slug": "soho",
    "timezone": "Europe/London",
    "service_ids": ["2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15"],
    "duration": { "minutes": 30, "display": "30 mins" },
    "slots": [
      {
        "starts_at": "2026-05-12T09:00:00+01:00",
        "ends_at": "2026-05-12T09:30:00+01:00",
        "staff_id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84"
      },
      {
        "starts_at": "2026-05-12T09:30:00+01:00",
        "ends_at": "2026-05-12T10:00:00+01:00",
        "staff_id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84"
      },
      {
        "starts_at": "2026-05-13T11:00:00+01:00",
        "ends_at": "2026-05-13T11:30:00+01:00",
        "staff_id": "c1d4f8a2-6e3b-49d7-a085-2b8c5f1e7d39"
      }
    ]
  }
}

Clients

Look up a returning client by phone or email, or create a new client record before booking.

GET/clients

Look up a client by phone or email. At least one parameter is required. Returns at most one client per match within an organisation.

Scope required · read

NameDescription
  • phone

    The full phone number including country code. UK numbers should be sent as +44…, not 0…

  • email

    Email address to search for. At least one of phone or email is required.

Response fields

NameDescription
  • id

    Unique identifier for the client.

  • first_name

    Client's first name.

  • last_name

    Client's last name.

  • phone

    Phone number including country code.

  • email

    Email address.

  • marketing_consent

    Whether the client has opted in to marketing communications.

  • created_at

    When the client record was created (UTC).

json200 OK · client found
{
  "data": {
    "id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "first_name": "Alex",
    "last_name": "Carter",
    "phone": "+447700900123",
    "email": "alex@example.com",
    "marketing_consent": false,
    "created_at": "2025-11-04T10:22:00Z"
  }
}
json404 Not Found · no match
{
  "error": {
    "code": "not_found",
    "message": "No client found matching the provided phone or email."
  }
}
POST/clients

Create a new client. At least one of phone or email is required. If a matching phone or email already exists in the organisation, the existing client is returned rather than a duplicate created.

Scope required · read_write

NameDescription
  • first_name

    required

    First name. 1 – 60 characters.

  • last_name

    required

    Last name. 1 – 60 characters.

  • phone

    Phone number, used for SMS reminders. At least one of phone or email is required.

  • email

    Email address, used for email reminders and receipts. At least one of phone or email is required.

  • marketing_consent

    Defaults to false. Only set to true if the client has actively opted in.

bashExample request
curl https://rest.setora.co.uk/v1/clients \
  -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Alex",
    "last_name": "Carter",
    "phone": "+447700900123",
    "email": "alex@example.com"
  }'
json201 Created
{
  "data": {
    "id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "first_name": "Alex",
    "last_name": "Carter",
    "phone": "+447700900123",
    "email": "alex@example.com",
    "marketing_consent": false,
    "created_at": "2026-05-01T12:00:00Z"
  }
}
json200 OK · existing client returned
{
  "data": {
    "id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "first_name": "Alex",
    "last_name": "Carter",
    "phone": "+447700900123",
    "email": "alex@example.com",
    "marketing_consent": false,
    "created_at": "2025-11-04T10:22:00Z"
  }
}

Bookings

Create, fetch, reschedule and cancel bookings. POST /bookings supports an Idempotency-Key for safe retries.

Booking statuses
  • pending_payment

    Booking created but a deposit is required. The client should be directed to the payment_url. The booking will move to confirmed once the deposit is paid.

  • pending_confirmation

    Slot is held while the customer completes a required step on the public Setora booking page (deposit, Booking Protection card setup, or both). The hold expires after 15 minutes if not finalised. See pending_confirmation.requirements on the booking response.

  • confirmed

    All required steps completed (or none required). The booking is live and visible to staff in the Hub.

  • completed

    The appointment has been fulfilled. Set by staff in the Hub after the service is delivered.

  • no_show

    The client did not attend. Set by staff in the Hub. The deposit is not refunded, and the Booking Protection no-show fee may apply if the shop has it enabled.

  • cancelled

    The booking was cancelled. Whether the deposit was refunded depends on the shop's cancellation policy window. A late cancellation may trigger the Booking Protection late-cancel fee if the shop has it enabled.

  • expired

    A pending_confirmation hold timed out. The slot is released, no cancellation email is sent, and no charge is taken. Expired bookings do not block availability.

POST/bookings

Create a booking. If the service requires a deposit, the response includes a hosted payment_url. The booking is held in pending state until the deposit is paid; pass an Idempotency-Key for safe retries.

Scope required · read_write

NameDescription
  • location_slug

    required

    The slug of the location the booking is for.

  • service_ids

    required

    One or more service IDs. Each must be bookable online at the location.

  • staff_id

    Optional. Omit to use any-staff assignment. Must be eligible for all requested services.

  • client_id

    required

    An existing client. Use POST /clients first if needed.

  • starts_at

    required

    Slot start time. Must match a slot returned by /availability.

  • notes

    Optional. Free text shown to staff in the Hub.

  • checkout_success_url

    Optional. Where to redirect the customer after a successful deposit payment. Supports {booking_reference} and {location_slug} placeholders. Only used when a deposit is required. Defaults to the Setora confirmation page if omitted.

  • checkout_cancel_url

    Optional. Where to redirect the customer if they abandon the deposit checkout. Only used when a deposit is required. Defaults to the Setora booking page if omitted.

When a deposit is required, Setora creates a Stripe hosted checkout session and returns a payment_url. Direct your customer to that URL to collect payment. Once paid, Stripe confirms payment via webhook and the booking status moves to confirmed. The customer is then redirected to your checkout_success_url. Use the {booking_reference} placeholder in your URL so you can identify which booking was paid — for example https://yourapp.com/bookings/confirmed?ref={booking_reference}.

Response fields

NameDescription
  • reference

    Human-readable booking reference.

  • status

    Current booking status. See the status table above for all possible values.

  • deposit

    Present when a deposit is required. Contains amount (minor units) and currency.

  • payment_url

    Present when a deposit is required. A hosted checkout page the client should be directed to in order to pay the deposit. The booking remains in pending_payment until payment completes.

  • notes

    Free text passed at creation. Shown to staff in the Hub.

  • created_at

    When the booking was created (UTC).

  • updated_at

    When the booking was last modified (UTC).

bashExample request
curl https://rest.setora.co.uk/v1/bookings \
  -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: 6d0e2f88-b7e5-4d6a-9f77-0b2bb2c4e9d1" \
  -H "Content-Type: application/json" \
  -d '{
    "location_slug": "soho",
    "service_ids": ["2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15"],
    "client_id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "starts_at": "2026-05-12T09:30:00+01:00",
    "checkout_success_url": "https://yourapp.com/bookings/confirmed?ref={booking_reference}",
    "checkout_cancel_url": "https://yourapp.com/bookings/new"
  }'
json201 Created · deposit required
{
  "data": {
    "reference": "SET-A1B2C3",
    "status": "pending_payment",
    "location_slug": "soho",
    "service_ids": ["2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15"],
    "staff_id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84",
    "client_id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "starts_at": "2026-05-12T09:30:00+01:00",
    "ends_at": "2026-05-12T10:00:00+01:00",
    "deposit": { "amount": 500, "currency": "GBP" },
    "payment_url": "https://pay.setora.co.uk/checkout/cs_live_...",
    "notes": null,
    "created_at": "2026-04-30T15:01:22Z",
    "updated_at": "2026-04-30T15:01:22Z"
  }
}
GET/bookings/{reference}

Fetch a single booking by reference, including current status and any associated payment URL.

Scope required · read

Response fields

NameDescription
  • reference

    Human-readable booking reference (e.g. SET-A1B2C3).

  • status

    Current booking status. See the status table above.

  • location_slug

    The location the booking is at.

  • service_ids

    Services included in this booking.

  • staff_id

    The assigned staff member.

  • client_id

    The client who made the booking.

  • starts_at

    Appointment start time in the location's local timezone.

  • ends_at

    Appointment end time in the location's local timezone.

  • notes

    Free text passed at creation. Shown to staff in the Hub.

  • pending_confirmation

    Present when status is pending_confirmation. Contains requirements (array of strings, e.g. 'deposit', 'protection_setup') and expires_at (ISO 8601). Cleared once the hold is finalised or expires.

  • late_cancellation_fee

    Read-only Booking Protection state for the late cancellation fee. See the Booking Protection section below for the object shape. null when the shop does not have Booking Protection enabled for this booking.

  • no_show_fee

    Read-only Booking Protection state for the no-show fee. Same shape as late_cancellation_fee.

  • created_at

    When the booking was created (UTC).

  • updated_at

    When the booking was last modified (UTC).

json200 OK
{
  "data": {
    "reference": "SET-A1B2C3",
    "status": "confirmed",
    "location_slug": "soho",
    "service_ids": ["2a7c9e34-58b1-41f6-8d92-6e4a3b0c7d15"],
    "staff_id": "9b3e7d2c-4a1f-48e6-bc52-7f0a9d3b1e84",
    "client_id": "5d9f2a18-7c4e-46b3-a821-3e0b4c8d1f72",
    "starts_at": "2026-05-12T09:30:00+01:00",
    "ends_at": "2026-05-12T10:00:00+01:00",
    "notes": null,
    "pending_confirmation": null,
    "late_cancellation_fee": {
      "status": null,
      "currency": "GBP",
      "fee_percentage": 50,
      "amount_minor": 1250,
      "deposit_credit_minor": 0,
      "charge_amount_minor": 1250,
      "payment_method_saved": true,
      "failure_reason": null,
      "processed_at": null
    },
    "no_show_fee": {
      "status": null,
      "currency": "GBP",
      "fee_percentage": 100,
      "amount_minor": 2500,
      "deposit_credit_minor": 0,
      "charge_amount_minor": 2500,
      "payment_method_saved": true,
      "failure_reason": null,
      "processed_at": null
    },
    "created_at": "2026-04-30T15:01:22Z",
    "updated_at": "2026-04-30T15:02:08Z"
  }
}
PATCH/bookings/{reference}

Reschedule a booking. The new start must be a valid slot for the same service at the same location. Cancellation policy windows are enforced, and late changes return a 409.

Scope required · read_write

NameDescription
  • starts_at

    required

    The new slot start time.

  • staff_id

    Optional. Reassign to a different eligible staff member.

POST/bookings/{reference}/cancel

Cancel a booking. The shop's cancellation policy controls whether a deposit is refunded. The endpoint never mutates the deposit on its own; it only triggers the policy.

Scope required · read_write

NameDescription
  • reason

    Optional. Free text shown to staff. Useful when an AI agent records the client's stated reason.

Response fields

NameDescription
  • reference

    The booking reference.

  • status

    Always "cancelled" on success.

  • cancelled_at

    When the cancellation was processed (UTC).

  • deposit_refunded

    Whether the deposit was refunded, based on the shop's cancellation policy window.

json200 OK
{
  "data": {
    "reference": "SET-A1B2C3",
    "status": "cancelled",
    "cancelled_at": "2026-05-11T18:14:00Z",
    "deposit_refunded": false
  }
}

Booking Protection

Read-only fields on the booking resource describing Booking Protection state. v1 does not provide endpoints to create card setup flows.

v1 scope · read-only

REST API v1 exposes Booking Protection state as read-only fields on the booking resource. It does not provide endpoints to create SetupIntents, save payment methods, capture explicit consent, or trigger off-session protection charges. Customers complete card setup on the public Setora booking page; staff charge or waive fees in the Hub. API consumers see the resulting state on the booking response.

BookingProtectionFee fields

The late_cancellation_fee and no_show_fee objects share the same shape. Either may be null when Booking Protection is not enabled for the shop or the booking.

NameDescription
  • status

    One of charged, waived, deposit_covered, failed, or null. null means no action has been taken yet (booking is still active or was completed normally).

  • currency

    Currency for the fee amounts.

  • fee_percentage

    Percentage of the booking's service total used to calculate the fee (e.g. 50 for 50%). Snapshotted from the policy at booking creation.

  • amount_minor

    Calculated fee in minor currency units, before any deposit credit. Capped at the booking's service total.

  • deposit_credit_minor

    Portion of amount_minor covered by a retained deposit. Zero if no deposit was retained.

  • charge_amount_minor

    Net amount Setora attempted (or would attempt) to charge the saved card: amount_minor minus deposit_credit_minor.

  • payment_method_saved

    Whether a payment method is currently saved on the booking that could be charged.

  • failure_reason

    Stripe failure reason when status is failed. null otherwise.

  • processed_at

    When the fee was charged, waived, or marked deposit_covered. null while no action has been taken.

pending_confirmation fields

Present on the booking response when status is pending_confirmation. Cleared (null) once the hold is finalised or expires.

NameDescription
  • requirements

    Outstanding steps the customer needs to complete on the public Setora booking page. Possible values include 'deposit' and 'protection_setup'. Both may appear together.

  • expires_at

    When the hold expires (UTC). After this point the booking moves to expired and the slot is released.

json200 OK · pending confirmation
{
  "data": {
    "reference": "SET-A1B2C3",
    "status": "pending_confirmation",
    "pending_confirmation": {
      "requirements": ["protection_setup"],
      "expires_at": "2026-05-12T09:45:00Z"
    },
    "late_cancellation_fee": {
      "status": null,
      "currency": "GBP",
      "fee_percentage": 50,
      "amount_minor": 1250,
      "deposit_credit_minor": 0,
      "charge_amount_minor": 1250,
      "payment_method_saved": false,
      "failure_reason": null,
      "processed_at": null
    },
    "no_show_fee": {
      "status": null,
      "currency": "GBP",
      "fee_percentage": 100,
      "amount_minor": 2500,
      "deposit_credit_minor": 0,
      "charge_amount_minor": 2500,
      "payment_method_saved": false,
      "failure_reason": null,
      "processed_at": null
    }
  }
}