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 Stripe-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: Hosted Stripe checkout session content: application/json: schema: $ref: "#/components/schemas/CheckoutSessionResponse" "400": description: Invalid request "401": description: Unauthorized /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 /v1/webhooks/stripe: post: tags: [Billing] operationId: handleStripeWebhook 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] properties: environment: type: string neonAuthEnabled: type: boolean apiUrl: type: string format: uri 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, locale, timezone, planCode, kpis] properties: tenantName: type: string locale: type: string timezone: type: string planCode: type: string kpis: type: array items: $ref: "#/components/schemas/DashboardKPI" TenantBootstrap: type: object required: [tenantId, tenantName, preset, locale, timezone, currentUser] properties: tenantId: type: string format: uuid tenantName: type: string preset: type: string locale: type: string timezone: type: string planCode: type: string 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 PlanEntitlements: type: object required: [maxLocations, maxStaff, smsAddonAvailable, advancedReporting] properties: maxLocations: type: integer maxStaff: type: integer smsAddonAvailable: type: boolean advancedReporting: type: boolean SubscriptionSnapshot: type: object required: [tenantId, customerId, subscriptionId, status, planCode, priceId, cancelAtPeriodEnd, entitlements] properties: tenantId: type: string format: uuid customerId: type: string subscriptionId: type: string status: type: string planCode: type: string 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" lastSyncedAt: type: string format: date-time nullable: true checkoutUrlAvailable: type: boolean CheckoutSessionRequest: type: object required: [planCode] properties: planCode: type: string enum: [starter, growth, multi-location] CheckoutSessionResponse: 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