API · Hearthline
Reservation endpoints, exactly as they ship.
The reservation resource is open to owners and operators — list bookings for a property, look one up by id, or flip a row from pending to confirmed / cancelled. Every shape below mirrors a real route handler in src/app/api/reservations/**; copy a request body and it parses against the live schema.
Authentication, today
The reservation API rides the same auth session as the owner dashboard — there is no separate API-key surface today. Owner-facing routes (list, confirm, cancel) read the better-auth session cookie. Anonymous routes (POST /api/reservations, GET /api/reservations/:id, POST /api/reservations/guest) accept any request; the cuid id used in the lookup is the only secret.
Integrators reusing Hearthline today authenticate as the property owner. A long-lived API-token story is a separate future piece; the docs will be updated here when it ships.
Resource catalogues
Three string enums drive the resource, mirrored from src/lib/contracts/reservation.ts. The DB column is a free-form String (no Prisma enum); handlers coerce unknown values via mapPayment().
status
- pending
- confirmed
- cancelled
paymentStatus
- pending_payment
- deposit_paid
- cancelled
- refunded
channel
- direct
- marketplace
Endpoints
Webhook events
There is no outbound webhook publisher from Hearthline today. The Stripe billing module is intentionally one-way — it does not accept inbound callbacks — and there is no separate booking.created / booking.status_changed delivery on this stack. Integrators that want to react to a new booking should poll GET /api/reservations on the cadence they need, or capture the reservationId from the POST /api/reservations response and look it up with GET /api/reservations/:id.
Webhook delivery — signed and retried — is on the roadmap. When it ships, the event shapes will be documented here alongside the existing catalogue.
Error catalogue
Every endpoint above returns errors as a flat { error | errors } JSON object. The same shapes feed the dashboard UI; recognising them in your client is the fastest way to surface a useful message.
| Shape | Status | When |
|---|---|---|
| { errors: { roomId: "…" } } | 400 | Validation failure from zod (bad roomId, nights out of range, malformed email, …). |
| { errors: { checkOut: "Those dates are no longer available." } } | 409 | Guest-path overlap — another reservation locks those dates on the room. |
| { error: "Unauthorized" } | 401 | Missing or invalid session on a gated route (list/confirm/cancel). |
| { error: "Forbidden" } | 403 | Authenticated but the session role is not "admin". |
| { error: "not_found" } | 404 | Reservation id is unknown (lookup/confirm/cancel), or roomId is unpublished (guest path). |
| { error: "payments_unavailable" } | 503 | Stripe billing is not enabled on this deploy — POST /api/reservations fails fast. |
| { error: "stripe_billing_not_configured" } | 503 | Stripe billing is enabled but the host has not finished onboarding. |
Future directions, not yet shipped
- check-in / check-out — the live schema carries only pending, confirmed, cancelled. A four-step lifecycle (pending → confirmed → checked-in → checked-out) needs a migration, not a route flag.
- Outbound webhooks — signed payloads for booking.created and booking.status_changed, with retry policy. Will live alongside this catalogue, not in a separate reference.
- API tokens — long-lived bearer tokens scoped to a single business, distinct from the owner session. The list endpoint today authenticates as the active owner; a token-scoped variant will sit beside it.