openapi: 3.1.0
info:
  title: FavCRM Public SDK API
  version: 0.2.0
  summary: Customer-facing v6 APIs for public FavCRM integrations.
  description: |
    Curated public SDK contract for building storefront, booking, member,
    content, event, commerce, offer, and survey integrations. Provider
    callbacks, generated platform docs, MCP/OAuth discovery, health checks, and
    internal developer routes are intentionally excluded.
servers:
  - url: https://api.favcrm.io
    description: Production v6 API domain
  - url: https://api.favoredapp.co
    description: Legacy production domain that can proxy v6 routes
  - url: http://localhost:18778
    description: Local development gateway
tags:
  - name: Service Booking
  - name: Member Session
  - name: Content
  - name: Events
  - name: Shop
  - name: Loyalty and Offers
  - name: Forms and Surveys
paths:
  /v6/customer-portal/services:
    get:
      tags: [Service Booking]
      summary: List bookable services
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SearchQuery'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/LimitQuery'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/services/{id}:
    get:
      tags: [Service Booking]
      summary: Get service detail
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '404': { $ref: '#/components/responses/Error' }
  /v6/customer-portal/services/{id}/staff:
    get:
      tags: [Service Booking]
      summary: List service staff
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/services/{id}/resources:
    get:
      tags: [Service Booking]
      summary: List service resources
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/services/{id}/slots:
    get:
      tags: [Service Booking]
      summary: List available slots
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
        - name: date
          in: query
          schema: { type: string, format: date }
        - name: staffId
          in: query
          schema: { type: string }
        - name: resourceId
          in: query
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/bookings/guest:
    post:
      tags: [Service Booking]
      summary: Create a guest booking
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GuestBookingRequest' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
        '400': { $ref: '#/components/responses/Error' }
  /v6/customer-portal/bookings:
    get:
      tags: [Service Booking]
      summary: List authenticated customer bookings
      security: [{ customerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/bookings/{id}:
    get:
      tags: [Service Booking]
      summary: Get authenticated customer booking detail
      security: [{ customerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/bookings/{id}/cancel:
    post:
      tags: [Service Booking]
      summary: Cancel authenticated customer booking
      security: [{ customerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/bookings/{id}/reschedule:
    post:
      tags: [Service Booking]
      summary: Reschedule authenticated customer booking
      security: [{ customerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/login-channel:
    get:
      tags: [Member Session]
      summary: Get tenant login channel
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/otp:
    post:
      tags: [Member Session]
      summary: Request customer OTP
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OtpRequest' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/otp/verify:
    post:
      tags: [Member Session]
      summary: Verify customer OTP
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OtpVerifyRequest' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/register:
    post:
      tags: [Member Session]
      summary: Register customer/member
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RegisterMemberRequest' }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/validate-token:
    post:
      tags: [Member Session]
      summary: Validate customer token
      security: [{ customerBearer: [] }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/refresh:
    post:
      tags: [Member Session]
      summary: Refresh customer token
      security: [{ customerBearer: [] }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/logout:
    post:
      tags: [Member Session]
      summary: Logout customer
      security: [{ customerBearer: [] }]
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/auth/registration-form:
    get:
      tags: [Member Session]
      summary: Get tenant registration form fields
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/cms/pages:
    get:
      tags: [Content]
      summary: List published pages
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/cms/pages/{slug}:
    get:
      tags: [Content]
      summary: Get published page by slug
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SlugPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/cms/posts:
    get:
      tags: [Content]
      summary: List published posts
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/cms/posts/{slug}:
    get:
      tags: [Content]
      summary: Get published post by slug
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SlugPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/cms/categories:
    get:
      tags: [Content]
      summary: List public CMS categories
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/cms/menus/{location}:
    get:
      tags: [Content]
      summary: Get CMS menu by location
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - name: location
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/events:
    get:
      tags: [Events]
      summary: List public events
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SearchQuery'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/events/{slug}:
    get:
      tags: [Events]
      summary: Get public event detail
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SlugPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/event-registrations:
    post:
      tags: [Events]
      summary: Register for event
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/event-registrations/{id}/payment-intent:
    post:
      tags: [Events]
      summary: Create event registration payment intent
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/event-registrations/{id}/access:
    get:
      tags: [Events]
      summary: Check event registration access
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/shop/products:
    get:
      tags: [Shop]
      summary: List products
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/products/{slug}:
    get:
      tags: [Shop]
      summary: Get product detail
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SlugPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/shop/categories:
    get:
      tags: [Shop]
      summary: List shop categories
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/brands:
    get:
      tags: [Shop]
      summary: List brands
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/collections:
    get:
      tags: [Shop]
      summary: List collections
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/shipping-methods:
    get:
      tags: [Shop]
      summary: List shipping methods
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/payment-methods:
    get:
      tags: [Shop]
      summary: List payment methods
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/shop/orders:
    get:
      tags: [Shop]
      summary: List authenticated customer orders
      security: [{ customerBearer: [] }]
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
    post:
      tags: [Shop]
      summary: Create shop order
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/shop/orders/{orderId}/payment-status:
    get:
      tags: [Shop]
      summary: Get order payment status
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - name: orderId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/membership-tiers:
    get:
      tags: [Loyalty and Offers]
      summary: List membership tiers
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/gifts/offers:
    get:
      tags: [Loyalty and Offers]
      summary: List gift offers
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/gifts/offers/{id}:
    get:
      tags: [Loyalty and Offers]
      summary: Get gift offer
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/gifts/offers/{id}/redeem:
    post:
      tags: [Loyalty and Offers]
      summary: Redeem gift offer
      security: [{ customerBearer: [] }]
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/gifts/claim:
    post:
      tags: [Loyalty and Offers]
      summary: Claim gift
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/gifts/redemptions:
    get:
      tags: [Loyalty and Offers]
      summary: List customer gift redemptions
      security: [{ customerBearer: [] }]
      responses:
        '200': { $ref: '#/components/responses/ListSuccess' }
  /v6/customer-portal/surveys/{slug}:
    get:
      tags: [Forms and Surveys]
      summary: Get public survey by slug
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/SlugPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/surveys/token/{token}:
    get:
      tags: [Forms and Surveys]
      summary: Get invitation-scoped survey
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/surveys/{id}/responses:
    post:
      tags: [Forms and Surveys]
      summary: Submit survey response
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/surveys/token/{token}/responses:
    post:
      tags: [Forms and Surveys]
      summary: Submit invitation-scoped survey response
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/storefront/resolve-domain:
    get:
      tags: [Forms and Surveys]
      summary: Resolve storefront domain to tenant context
      responses:
        '200': { $ref: '#/components/responses/Success' }
  /v6/customer-portal/contact:
    post:
      tags: [Forms and Surveys]
      summary: Submit public contact inquiry
      parameters:
        - $ref: '#/components/parameters/CompanyIdHeader'
      responses:
        '200': { $ref: '#/components/responses/Success' }
components:
  securitySchemes:
    customerBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    CompanyIdHeader:
      name: X-Company-Id
      in: header
      required: false
      schema: { type: string }
      description: Tenant id. May be omitted when tenant context is resolved by storefront domain.
    IdPath:
      name: id
      in: path
      required: true
      schema: { type: string }
    SlugPath:
      name: slug
      in: path
      required: true
      schema: { type: string }
    SearchQuery:
      name: search
      in: query
      required: false
      schema: { type: string }
    PageQuery:
      name: page
      in: query
      required: false
      schema: { type: integer, minimum: 1, default: 1 }
    LimitQuery:
      name: limit
      in: query
      required: false
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
  responses:
    Success:
      description: Standard success response
      content:
        application/json:
          schema: { $ref: '#/components/schemas/SuccessResponse' }
    ListSuccess:
      description: Standard list success response
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ListResponse' }
    Error:
      description: Standard error response
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
  schemas:
    SuccessResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data:
          type: object
          additionalProperties: true
    ListResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data:
          type: object
          properties:
            items:
              type: array
              items:
                type: object
                additionalProperties: true
            pagination:
              type: object
              additionalProperties: true
          additionalProperties: true
    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success: { type: boolean, const: false }
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    GuestBookingRequest:
      type: object
      required: [serviceId, occurrenceStart, occurrenceEnd, customerName, customerPhone]
      properties:
        serviceId: { type: string }
        occurrenceStart: { type: string, format: date-time }
        occurrenceEnd: { type: string, format: date-time }
        customerName: { type: string }
        customerPhone: { type: string }
        customerEmail: { type: string, format: email }
        staffId: { type: string }
        resourceId: { type: string }
    OtpRequest:
      type: object
      properties:
        phone: { type: string }
        email: { type: string, format: email }
    OtpVerifyRequest:
      type: object
      required: [code]
      properties:
        phone: { type: string }
        email: { type: string, format: email }
        code: { type: string }
    RegisterMemberRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        lastName: { type: string }
        phone: { type: string }
        email: { type: string, format: email }
        source: { type: string }
