Besmi·Developers

Besmi API · v1

Build on Besmi.

A small, clean API for building custom booking experiences on top of your Besmi business — a marketing site, an internal dashboard, a partner integration, anything. Same auth model, same data, just yours to compose.

Quick start

  1. i.
    Generate an API key. Sign in to Besmi, open Settings → Dev, click New key. Copy the key the moment it appears — Besmi only stores a hash, so it can't be shown again.
  2. ii.
    Make a request. Pass the key as a Bearer token. Every endpoint is scoped to your business — you never specify businessId.
  3. iii.
    Build whatever you want. Custom booking page, mobile app, agency dashboard, scheduled sync — all the same calls.

Your first request

curl https://besmi.com/api/v1/business \
  -H "Authorization: Bearer bsk_live_your_key_here"

Authentication

Every /api/v1/* endpoint accepts either an API key (recommended for server-to-server) or a Firebase ID token (used by the Besmi first-party UI). Both pass through the same auth pipeline. The businessId is derived from the token — never supplied as a parameter.

Header
Authorization: Bearer bsk_live_…
Key format
bsk_live_ prefix · 24 random bytes (url-safe base64) · ~40 characters total
Scopes
read for GET endpoints · write for POST / PATCH / DELETE
Storage
Besmi stores only a SHA-256 hash + a 16-character prefix for lookup. Plaintext surfaces exactly once on creation. If you lose a key, generate a new one.
Revocation
Soft-revoke from Settings → Dev. Requests with a revoked key return 401 on the next call.
Treat keys as production credentials. Each key inherits your business's full read & write scope. Don't embed in client-side JavaScript or commit to git. Use environment variables and rotate on team changes.

Endpoints

Base URL: https://besmi.com

GET/api/v1/business

Read business profile

Returns name, slug, contact, hours, timezone, theme, landing-page modules, and notification preferences. Stripe / Twilio / partner identifiers are scrubbed.

curl https://besmi.com/api/v1/business \
  -H "Authorization: Bearer bsk_live_…"

Response · 200

{
  "business": {
    "id":       "biz_xxx",
    "name":     "Your Esthi Bestie",
    "slug":     "mlc",
    "timezone": "America/Los_Angeles",
    "contact":  { "email": "...", "phone": "..." },
    "schedule":    { /* working hours */ },
    "theme":       { /* colors + fonts */ },
    "landingPage": { /* modules */ }
  }
}
GET/api/v1/services

List services & categories

Returns all services and their categories, in display order. Use this to populate a custom service picker.

curl https://besmi.com/api/v1/services \
  -H "Authorization: Bearer bsk_live_…"

Response · 200

{
  "categories": [
    { "id": "cat_xxx", "name": "Lashes", "order": 0, "color": "#C026D3", "services": [] }
  ],
  "services": [
    {
      "id":              "svc_xxx",
      "categoryId":      "cat_xxx",
      "name":            "Classic Full Set",
      "price":           18000,
      "durationMinutes": 120,
      "bufferMinutes":   15,
      "bookableOnline":  true,
      "addons":          []
    }
  ]
}
GET/api/v1/availability

Get available slots

Returns bookable time slots for a given date and service duration. Accounts for working hours, blocked windows, buffers, and existing appointments.

Query parameters

date
string
YYYY-MM-DD in the business's timezone
durationMinutes
number
Service duration. Default 60. Range 0–480.
curl "https://besmi.com/api/v1/availability?date=2026-05-15&durationMinutes=120" \
  -H "Authorization: Bearer bsk_live_…"

Response · 200

{
  "slots": [
    { "start": "2026-05-15T17:00:00.000Z", "end": "2026-05-15T19:00:00.000Z" },
    { "start": "2026-05-15T19:30:00.000Z", "end": "2026-05-15T21:30:00.000Z" }
  ]
}
GET/api/v1/appointments

List appointments

Lists appointments for the authenticated business, optionally filtered by date range and status. Cursored pagination via from / nextCursor.

Query parameters

from
string
ISO 8601 — inclusive lower bound on startAt
to
string
ISO 8601 — exclusive upper bound on startAt
status
string
Comma-separated subset of pending,confirmed,cancelled,noshow
limit
number
Default 50, max 200
curl "https://besmi.com/api/v1/appointments?from=2026-05-01&to=2026-06-01&status=confirmed,pending" \
  -H "Authorization: Bearer bsk_live_…"
POST/api/v1/appointments

Create an appointment

Books a new appointment on behalf of the authenticated business. Requires the write scope. Payment isn't handled here — use Besmi's built-in payment flow or your own Stripe Connect integration.

Request body

{
  "serviceId":  "svc_xxx",
  "startAt":    "2026-05-15T17:00:00.000Z",
  "endAt":      "2026-05-15T19:00:00.000Z",
  "client": {
    "firstName": "Jordan",
    "lastName":  "Lee",
    "email":     "jordan@example.com",
    "phone":     "+15555550100"
  },
  "addonIds": ["addon_xxx"],
  "status":   "confirmed",
  "note":     "Allergic to acrylic"
}
curl -X POST https://besmi.com/api/v1/appointments \
  -H "Authorization: Bearer bsk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "svc_xxx",
    "startAt":   "2026-05-15T17:00:00.000Z",
    "endAt":     "2026-05-15T19:00:00.000Z",
    "client": { "firstName": "Jordan", "email": "jordan@example.com", "phone": "+15555550100" }
  }'
GET/api/v1/appointments/{id}

Read one appointment

Fetches a single appointment by id.

curl https://besmi.com/api/v1/appointments/apt_xxx \
  -H "Authorization: Bearer bsk_live_…"
PATCH/api/v1/appointments/{id}

Update an appointment

Patch status, time, addons, owner note, or client contact. Payment fields are intentionally NOT patchable — use the dedicated Stripe surfaces so Stripe stays the source of truth.

Request body (any subset)

{
  "status":   "cancelled",
  "startAt":  "2026-05-16T17:00:00.000Z",
  "endAt":    "2026-05-16T19:00:00.000Z",
  "addonIds": ["addon_xxx"],
  "note":     "Client called to reschedule",
  "client":   { "phone": "+15555550101" }
}
curl -X PATCH https://besmi.com/api/v1/appointments/apt_xxx \
  -H "Authorization: Bearer bsk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "status": "cancelled" }'
GET/api/v1/customers

List customers

Returns customer records, optionally filtered by a name / email / phone substring.

Query parameters

q
string
Case-insensitive substring filter
limit
number
Default 100, max 500
curl "https://besmi.com/api/v1/customers?q=jordan&limit=20" \
  -H "Authorization: Bearer bsk_live_…"

Errors

Standard HTTP status codes. The response body is always JSON with an error field on failures.

400

Bad Request — malformed body, invalid date, missing required field

401

Unauthorized — missing, invalid, or revoked key

403

Forbidden — key lacks the required scope

404

Not Found — resource doesn't exist or belongs to another business

500

Internal — something went wrong on our side. Safe to retry.

WebhooksComing soon

Subscribe to appointment.created, appointment.updated, and customer.created events. HMAC-SHA256 signed (Stripe-style). Email developers@besmi.com if you need this on a timeline.

Help

Question, bug report, or integration request? Email developers@besmi.com. Common questions live in the FAQ; this page is the source of truth for endpoint shapes.