Files
Bookra/apps/backend/openapi/bookra.openapi.yaml
T
Tomas Dvorak 48c3e15a38 cleanup
2026-05-05 09:48:07 +02:00

701 lines
17 KiB
YAML

openapi: 3.0.3
info:
title: Bookra API
version: 0.1.0
description: >
Remote-first booking API for Bookra. The Go backend owns business rules,
scheduling logic, tenant isolation, and Paddle-backed plan enforcement.
servers:
- url: http://localhost:8080
tags:
- name: Health
- name: Public Booking
- name: Dashboard
- name: Tenant
- name: Billing
- name: Jobs
paths:
/healthz:
get:
tags: [Health]
operationId: getHealth
responses:
"200":
description: Service health
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
/v1/meta/config:
get:
tags: [Health]
operationId: getPublicConfig
responses:
"200":
description: Public runtime configuration
content:
application/json:
schema:
$ref: "#/components/schemas/PublicConfig"
/v1/public/tenants/{tenantSlug}/availability:
get:
tags: [Public Booking]
operationId: getPublicAvailability
parameters:
- in: path
name: tenantSlug
required: true
schema:
type: string
responses:
"200":
description: Availability for a tenant booking page
content:
application/json:
schema:
$ref: "#/components/schemas/PublicAvailabilityResponse"
/v1/public/bookings:
post:
tags: [Public Booking]
operationId: createBooking
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateBookingRequest"
responses:
"201":
description: Booking created
content:
application/json:
schema:
$ref: "#/components/schemas/CreateBookingResponse"
/v1/dashboard/summary:
get:
tags: [Dashboard]
operationId: getDashboardSummary
security:
- bearerAuth: []
responses:
"200":
description: Tenant dashboard summary
content:
application/json:
schema:
$ref: "#/components/schemas/DashboardSummary"
"401":
description: Unauthorized
/v1/tenants/bootstrap:
get:
tags: [Tenant]
operationId: getTenantBootstrap
security:
- bearerAuth: []
responses:
"200":
description: Tenant bootstrap payload for the authenticated user
content:
application/json:
schema:
$ref: "#/components/schemas/TenantBootstrap"
"401":
description: Unauthorized
/v1/tenants/onboard:
post:
tags: [Tenant]
operationId: onboardTenant
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OnboardTenantRequest"
responses:
"201":
description: Tenant created for authenticated user
content:
application/json:
schema:
$ref: "#/components/schemas/TenantBootstrap"
"400":
description: Invalid request
"401":
description: Unauthorized
"409":
description: Conflict
/v1/billing/subscription:
get:
tags: [Billing]
operationId: getSubscriptionSnapshot
security:
- bearerAuth: []
responses:
"200":
description: Current subscription snapshot and entitlements
content:
application/json:
schema:
$ref: "#/components/schemas/SubscriptionSnapshot"
"401":
description: Unauthorized
/v1/billing/checkout:
post:
tags: [Billing]
operationId: createBillingCheckout
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CheckoutSessionRequest"
responses:
"200":
description: Paddle checkout launch payload
content:
application/json:
schema:
$ref: "#/components/schemas/CheckoutLaunchResponse"
"400":
description: Invalid request
"401":
description: Unauthorized
"503":
description: Billing provider unavailable
/v1/billing/refresh:
post:
tags: [Billing]
operationId: refreshSubscriptionSnapshot
security:
- bearerAuth: []
responses:
"200":
description: Refreshed snapshot
content:
application/json:
schema:
$ref: "#/components/schemas/SubscriptionSnapshot"
"401":
description: Unauthorized
"503":
description: Billing provider unavailable
/v1/billing/portal:
post:
tags: [Billing]
operationId: createBillingPortalSession
security:
- bearerAuth: []
responses:
"200":
description: Paddle customer portal session
content:
application/json:
schema:
$ref: "#/components/schemas/PortalSessionResponse"
"400":
description: Billing customer missing
"401":
description: Unauthorized
"503":
description: Billing provider unavailable
/v1/webhooks/paddle:
post:
tags: [Billing]
operationId: handlePaddleWebhook
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Webhook accepted
/v1/internal/jobs/reminders/dispatch:
post:
tags: [Jobs]
operationId: dispatchReminderJobs
security:
- jobRunnerKey: []
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/DispatchReminderJobsRequest"
responses:
"200":
description: Reminder jobs dispatched
content:
application/json:
schema:
$ref: "#/components/schemas/DispatchReminderJobsResponse"
"401":
description: Unauthorized
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
jobRunnerKey:
type: apiKey
in: header
name: X-Bookra-Job-Key
schemas:
HealthResponse:
type: object
required: [status, environment]
properties:
status:
type: string
example: ok
environment:
type: string
example: staging
databaseConfigured:
type: boolean
PublicConfig:
type: object
required: [environment, neonAuthEnabled, apiUrl, demoMode]
properties:
environment:
type: string
neonAuthEnabled:
type: boolean
apiUrl:
type: string
format: uri
demoMode:
type: boolean
TimeSlot:
type: object
required: [startsAt, endsAt, mode]
properties:
serviceId:
type: string
format: uuid
nullable: true
classSessionId:
type: string
format: uuid
nullable: true
staffId:
type: string
format: uuid
nullable: true
locationId:
type: string
format: uuid
nullable: true
startsAt:
type: string
format: date-time
endsAt:
type: string
format: date-time
mode:
type: string
enum: [appointment, class]
label:
type: string
remainingCapacity:
type: integer
nullable: true
PublicAvailabilityResponse:
type: object
required: [tenantSlug, timezone, locale, slots]
properties:
tenantSlug:
type: string
timezone:
type: string
locale:
type: string
enum: [cs, en]
slots:
type: array
items:
$ref: "#/components/schemas/TimeSlot"
CreateBookingRequest:
type: object
required: [tenantSlug, bookingMode, customerName, customerEmail, startsAt, endsAt]
properties:
tenantSlug:
type: string
bookingMode:
type: string
enum: [appointment, class]
serviceId:
type: string
format: uuid
classSessionId:
type: string
format: uuid
staffId:
type: string
format: uuid
locationId:
type: string
format: uuid
customerName:
type: string
customerEmail:
type: string
format: email
notes:
type: string
startsAt:
type: string
format: date-time
endsAt:
type: string
format: date-time
CreateBookingResponse:
type: object
required: [bookingId, reference, status]
properties:
bookingId:
type: string
format: uuid
reference:
type: string
status:
type: string
enum: [confirmed, waitlisted]
DashboardKPI:
type: object
required: [code, label, value]
properties:
code:
type: string
label:
type: string
value:
type: string
DashboardSummary:
type: object
required: [tenantName, tenantSlug, locale, timezone, planCode, publicBookingUrl, setupCompletion, kpis]
properties:
tenantName:
type: string
tenantSlug:
type: string
locale:
type: string
timezone:
type: string
planCode:
type: string
publicBookingUrl:
type: string
setupCompletion:
type: integer
kpis:
type: array
items:
$ref: "#/components/schemas/DashboardKPI"
upcomingBookings:
type: array
items:
$ref: "#/components/schemas/UpcomingBooking"
widgetSnippets:
type: array
items:
$ref: "#/components/schemas/WidgetSnippet"
tracking:
$ref: "#/components/schemas/TrackingStatus"
UpcomingBooking:
type: object
properties:
reference:
type: string
customerName:
type: string
customerEmail:
type: string
startsAt:
type: string
format: date-time
endsAt:
type: string
format: date-time
status:
type: string
label:
type: string
WidgetSnippet:
type: object
properties:
kind:
type: string
code:
type: string
TrackingStatus:
type: object
properties:
provider:
type: string
connected:
type: boolean
siteId:
type: string
message:
type: string
BrandProfile:
type: object
properties:
name:
type: string
siteUrl:
type: string
logoUrl:
type: string
primaryColor:
type: string
TenantBootstrap:
type: object
required: [tenantId, tenantName, preset, locale, timezone, currentUser]
properties:
tenantId:
type: string
format: uuid
tenantName:
type: string
tenantSlug:
type: string
preset:
type: string
locale:
type: string
timezone:
type: string
planCode:
type: string
onboardingCompleted:
type: boolean
brand:
$ref: "#/components/schemas/BrandProfile"
currentUser:
type: object
required: [subject, role]
properties:
subject:
type: string
email:
type: string
format: email
name:
type: string
role:
type: string
OnboardTenantRequest:
type: object
required: [name, slug, preset, locale, timezone]
properties:
name:
type: string
slug:
type: string
preset:
type: string
enum: [salon, clinic, massage, repair, studio]
locale:
type: string
enum: [cs, en]
timezone:
type: string
brand:
$ref: "#/components/schemas/BrandProfile"
locationName:
type: string
bookingDefaults:
$ref: "#/components/schemas/BookingDefaultsRequest"
availabilityBlocks:
type: array
items:
$ref: "#/components/schemas/AvailabilityBlockRequest"
teamInvites:
type: array
items:
$ref: "#/components/schemas/TeamInviteRequest"
BookingDefaultsRequest:
type: object
properties:
serviceName:
type: string
durationMinutes:
type: integer
bufferBeforeMinutes:
type: integer
bufferAfterMinutes:
type: integer
cancelWindowHours:
type: integer
AvailabilityBlockRequest:
type: object
required: [dayOfWeek, startsLocal, endsLocal]
properties:
dayOfWeek:
type: integer
minimum: 1
maximum: 7
startsLocal:
type: string
example: "09:00"
endsLocal:
type: string
example: "17:00"
busy:
type: boolean
TeamInviteRequest:
type: object
required: [email]
properties:
email:
type: string
format: email
role:
type: string
PlanEntitlements:
type: object
required: [maxLocations, maxStaff, emailReminders, advancedReporting, widgetEmbedding, umamiTracking]
properties:
maxLocations:
type: integer
maxStaff:
type: integer
emailReminders:
type: boolean
widgetEmbedding:
type: boolean
umamiTracking:
type: boolean
advancedReporting:
type: boolean
SubscriptionSnapshot:
type: object
required: [tenantId, provider, customerId, subscriptionId, status, planCode, currency, priceId, cancelAtPeriodEnd, entitlements, displayPrices, trialDays, checkoutUrlAvailable, syncAvailable, portalAvailable]
properties:
tenantId:
type: string
format: uuid
provider:
type: string
example: paddle
customerId:
type: string
subscriptionId:
type: string
status:
type: string
planCode:
type: string
currency:
type: string
enum: [czk, usd, eur]
priceId:
type: string
cancelAtPeriodEnd:
type: boolean
currentPeriodStart:
type: string
format: date-time
nullable: true
currentPeriodEnd:
type: string
format: date-time
nullable: true
paymentMethodBrand:
type: string
paymentMethodLast4:
type: string
entitlements:
$ref: "#/components/schemas/PlanEntitlements"
displayPrices:
type: array
items:
$ref: "#/components/schemas/PlanDisplayPrice"
trialDays:
type: integer
lastSyncedAt:
type: string
format: date-time
nullable: true
checkoutUrlAvailable:
type: boolean
syncAvailable:
type: boolean
portalAvailable:
type: boolean
CheckoutSessionRequest:
type: object
required: [planCode]
properties:
planCode:
type: string
enum: [starter, pro, business]
currency:
type: string
enum: [czk, usd]
PlanDisplayPrice:
type: object
required: [currency, amountCents, formatted]
properties:
currency:
type: string
enum: [czk, usd]
amountCents:
type: integer
formatted:
type: string
CheckoutLaunchResponse:
type: object
required: [priceId, successRedirectUrl, cancelRedirectUrl, customData]
properties:
priceId:
type: string
customerId:
type: string
customerEmail:
type: string
format: email
successRedirectUrl:
type: string
format: uri
cancelRedirectUrl:
type: string
format: uri
customData:
type: object
additionalProperties:
type: string
PortalSessionResponse:
type: object
required: [url]
properties:
url:
type: string
format: uri
DispatchReminderJobsRequest:
type: object
properties:
limit:
type: integer
minimum: 1
maximum: 200
DispatchReminderJobsResponse:
type: object
required: [processedCount, sentCount, failedCount]
properties:
processedCount:
type: integer
sentCount:
type: integer
failedCount:
type: integer