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.

Base URL — your Hearthline origin
All money is integer cents in USD
Dates are YYYY-MM-DD in the property's local timezone

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.

ShapeStatusWhen
{ errors: { roomId: "…" } }400Validation failure from zod (bad roomId, nights out of range, malformed email, …).
{ errors: { checkOut: "Those dates are no longer available." } }409Guest-path overlap — another reservation locks those dates on the room.
{ error: "Unauthorized" }401Missing or invalid session on a gated route (list/confirm/cancel).
{ error: "Forbidden" }403Authenticated but the session role is not "admin".
{ error: "not_found" }404Reservation id is unknown (lookup/confirm/cancel), or roomId is unpublished (guest path).
{ error: "payments_unavailable" }503Stripe billing is not enabled on this deploy — POST /api/reservations fails fast.
{ error: "stripe_billing_not_configured" }503Stripe 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.