Referensi API
Konvensi base URL, envelope respons ok/data/meta, kode error, versioning, lalu endpoint per-resource Termilo: Services, Availability, Bookings, dan Customers.
Base URL & konvensi#
Worker melayani path dashboard/public secara langsung serta partner API versioned pada /v1. Semua path berikut live:
| Method | Path | Catatan |
|---|---|---|
| POST | /book/* | Booking publik: config, availability, create. |
| POST | /booking/manage/* | Self-service customer via manage token. |
| GET | /api/* | Dashboard — butuh sesi (Google login) + RBAC. |
| GET | /api/integrations/* | OAuth & aksi provider (Google, Zoom). |
| GET | /v1/* | Partner API — Bearer key + X-Termilo-Tenant-Id. |
| POST | /mcp/:tenantId | Branding MCP — Streamable HTTP + Bearer key. |
| GET | /health | Health check. |
/v1/* mewajibkan Authorization: Bearer dan X-Termilo-Tenant-Id. Branding MCP memakai tenant ID pada path dan didokumentasikan di /docs/mcp.Envelope respons#
Setiap respons dibungkus envelope yang sama. Ini adalah bentuk nyata di kode (lib/http/response.ts): sukses membawa data, error membawa error, dan keduanya selalu membawa meta.requestId.
Sukses
Error
requestId juga dikembalikan di header respons x-request-id — ia meneruskan x-request-id masuk (≤128 karakter) atau membuat UUID baru. Sertakan dalam laporan masalah.Kode error#
Error validasi memakai code VALIDATION_ERROR (status 400) dengan details berisi [{path, message}] dari Zod. Kode berikut sudah dipancarkan di kode:
| code | HTTP | Arti |
|---|---|---|
VALIDATION_ERROR | 400 | Body/query gagal validasi Zod. details = [{path, message}]. |
AUTH_REQUIRED | 401 | Tidak ada sesi pada endpoint dashboard. |
SESSION_EXPIRED | 401 | Sesi dashboard sudah lewat masa berlaku. |
CSRF_INVALID | 403 | Header X-Termilo-CSRF tidak cocok pada request yang mengubah data. |
FORBIDDEN | 403 | Role tidak punya izin (RBAC). |
NOT_FOUND | 404 | Resource tidak ditemukan pada tenant ini. |
TENANT_INACTIVE | 403 | Tenant disabled atau suspended. |
SERVICE_INACTIVE | 403 | Layanan tidak active untuk dibooking. |
BOOKING_SLOT_UNAVAILABLE | 409 | Slot sudah terisi saat dicek ulang di bawah lock. |
BOOKING_POLICY_BLOCKED | 409 / 503 | Transisi lifecycle tidak diizinkan, atau lock tak tersedia. |
OAUTH_STATE_INVALID | 403 | State OAuth provider tidak valid. |
OAUTH_TOKEN_EXCHANGE_FAILED | 403 | Penukaran token provider gagal. |
CONFIG_MISSING | 500 | Konfigurasi server wajib tidak ada. |
INTERNAL_ERROR | 500 | Galat tak terduga. |
ORIGIN_NOT_ALLOWED, BOOKING_IDEMPOTENCY_CONFLICT, PAYMENT_REQUIRED, PROVIDER_NOT_CONNECTED, PROVIDER_NEEDS_REAUTH, PROVIDER_ACTION_PENDING, PROVIDER_ACTION_FAILED, RATE_LIMITED.Versioning#
Selama MVP, route internal boleh tanpa versi karena frontend dan backend dirilis bersamaan. Aturan yang mengikat:
- Payload API publik dan webhook membawa versi payload sejak awal.
- Prefix
/v1dipakai saat partner eksternal mengonsumsi route langsung lewatapi.termilo.id. - Satu versi maju saja. Tidak ada
/v2.
Services#
Layanan (event type) adalah hal yang dipesan. Endpoint dashboard, butuh sesi + RBAC. Dalam standar penamaan Termilo istilahnya event type; di skema dan admin API resource-nya secara harfiah bernama services.
Sudah aktif
| Method | Path | Catatan |
|---|---|---|
| GET | /api/services | List layanan. Role baca. |
| POST | /api/services | Buat (201). Wajib slug, name, durationMin. Tulis + CSRF. |
| PATCH | /api/services/:serviceId | Update sebagian (≥1 field). Tulis + CSRF. |
Field body (POST /api/services)
Record ServiceAdminRecord mengembalikan: serviceId, tenantId, categoryId, slug, name, description, durationMin, bufferBeforeMin, bufferAfterMin, priceAmount, priceCurrency, status ("active" | "hidden" | "disabled"), createdAtMs, updatedAtMs. Buffer default 0; status default active.
Staff & Resources (modul yang sama)
| Method | Path | Catatan |
|---|---|---|
| GET | /api/staff | List staf. POST membuat (wajib displayName). |
| PATCH | /api/staff/:staffId | Update sebagian. Tulis + CSRF. |
| GET | /api/resources | List resource. POST membuat (wajib type, name; capacity default 1). |
| PATCH | /api/resources/:resourceId | Update sebagian. Tulis + CSRF. |
StaffAdminRecord: staffId, tenantId, userId, displayName, email, timezone, status, createdAtMs, updatedAtMs. ResourceAdminRecord: resourceId, tenantId, type, name, locationLabel, capacity, status, ….
Availability#
Slot dihitung dari aturan dikurangi hari libur, blokir waktu, dan booking yang ada. Admin menulis jam operasional, blokir, dan hari libur; pembaca publik membaca slot tanpa auth. Sudah aktif
Admin (sesi + RBAC)
| Method | Path | Catatan |
|---|---|---|
| GET | /api/availability-rules | List jam operasional. POST membuat (validasi startMinute < endMinute). |
| PATCH | /api/availability-rules/:ruleId | Update aturan mingguan. |
| GET | /api/blocked-times | List blokir waktu. POST membuat. |
| PATCH | /api/blocked-times/:blockId | Update blokir. |
| GET | /api/holidays | List hari libur. POST membuat. |
| PATCH | /api/holidays/:holidayId | Update hari libur. |
AvailabilityRuleAdminRecord: ruleId, tenantId, ownerType ("tenant" | "staff" | "resource"), ownerId, weekday (0-6), startMinute, endMinute, timezone, effectiveFromAtMs, effectiveUntilAtMs, status. Waktu-hari adalah menit integer; weekday 0–6.
Baca publik (tanpa auth)
| Method | Path | Catatan |
|---|---|---|
| GET | /book/:tenantSlug/availability | Baca slot publik (tanpa auth). Query serviceSlug, fromMs, toMs. Jendela maks 31 hari. |
"finalAuthority": "booking_create_recheck_under_lock". Slot adalah kandidat untuk ditampilkan; ketersediaan final ditentukan ulang di bawah lock saat booking dibuat. Jendela query dibatasi 31 hari.Bookings#
Tiga jalur: buat booking publik, kelola via manage token customer, dan lifecycle dashboard. Sudah aktif
Config & create publik (tanpa auth)
| Method | Path | Catatan |
|---|---|---|
| GET | /book/:tenantSlug/config | Tenant + daftar layanan publik (tanpa auth). |
| POST | /book/:tenantSlug/bookings | Buat booking. Header Idempotency-Key wajib. 201 baru / 200 replay. |
Field body untuk POST /book/:tenantSlug/bookings (email ATAU phone wajib):
held dengan holdExpiresAtMs = now + 10 menit. Slot dicek ulang di bawah Durable Object lock; jika tidak cocok → BOOKING_SLOT_UNAVAILABLE (409). Replay idempoten mengembalikan 200 (bukan 201).Manage customer (token bertanda tangan, tanpa sesi)
| Method | Path | Catatan |
|---|---|---|
| GET | /booking/manage/:token | Detail booking + actions {canCancel, canReschedule}. cache-control: private, no-store. |
| POST | /booking/manage/:token/cancel | Batalkan via manage token bertanda tangan. |
| POST | /booking/manage/:token/reschedule | Body startsAtMs. Re-lock + cek tumpang-tindih. |
manageToken dan manageUrl dikembalikan saat booking dibuat. Token tidak dapat menjangkau /api/* dan tidak membuat sesi.
Lifecycle dashboard (sesi + RBAC)
| Method | Path | Catatan |
|---|---|---|
| GET | /api/bookings | List. Query status, fromMs, toMs, limit (default 50, maks 100). |
| GET | /api/bookings/:bookingId | Detail satu booking. |
| POST | /api/bookings/:bookingId/confirm | held → confirmed. |
| POST | /api/bookings/:bookingId/cancel | Body reason. held | confirmed → cancelled. |
| POST | /api/bookings/:bookingId/complete | confirmed → completed. |
| POST | /api/bookings/:bookingId/no-show | Body reason. confirmed → no_show. |
| POST | /api/bookings/:bookingId/reschedule | Body startsAtMs. |
Status: held, confirmed, cancelled, completed, no_show. Terminal: cancelled, completed, no_show. Transisi yang tidak diizinkan mengembalikan BOOKING_POLICY_BLOCKED.
Customers#
/api/customers sudah aktif untuk sesi dashboard dan memakai D1 tenant aktif. API publik partner/server-to-server tetap menunggu host /v1.| Method | Path | Catatan |
|---|---|---|
| GET | /api/customers | List customer tenant. Query q, limit, cursor. |
| GET | /api/customers/:customerId | Detail customer tenant. |
| POST | /api/customers | Buat customer manual. Wajib name; email atau phone opsional sesuai data tenant. |
| PATCH | /api/customers/:customerId | Update sebagian customer. Tulis + CSRF. |
Field tabel customers yang dipetakan ke dashboard: customerId, tenantId, primaryEmail (nullable), phone (nullable), name, locale, timezone. Pembuatan booking mensyaratkan email ATAU phone.