openapi: 3.1.0
info:
  title: SleekSync Private API
  version: 1.1.0-pilot
  summary: Controlled tenant-scoped integration API
  description: >-
    Server-side API for approved SleekSync tenants. Access is disabled by
    default and is not a generally available public API. It supports a
    published catalog, rental policy, current and date-scoped availability,
    guarded inventory-level writes, and inquiry intake. It does not create
    holds, orders, or payments.
  contact:
    name: SleekSync integration review
    email: info@sleeksyncpos.com
  license:
    name: Proprietary
    url: https://www.sleeksyncpos.com/terms-of-service
servers:
  - url: "{baseUrl}"
    description: Base URL assigned in POS Settings > Integrations > API Access
    variables:
      baseUrl:
        default: https://YOUR_PROJECT.supabase.co/functions/v1/api-v1
security:
  - bearerAuth: []
tags:
  - name: Connection
    description: Credential and tenant-access verification.
  - name: Rental
    description: Rental policy and inventory availability reads.
  - name: Inventory
    description: Compare-and-set inventory synchronization for approved pilots.
  - name: Catalog
    description: Ecommerce-approved product data.
  - name: Inquiries
    description: Inquiry-first website intake for staff follow-up.
paths:
  /health:
    get:
      operationId: getPrivateApiHealth
      summary: Verify the key and tenant API switch
      tags: [Connection]
      responses:
        "200":
          description: The key and tenant API access are active.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
  /rental/policy:
    get:
      operationId: getRentalPolicy
      summary: Read the effective tenant or store rental policy
      description: Requires the `inventory:read` scope.
      tags: [Rental]
      parameters:
        - $ref: "#/components/parameters/StoreId"
      responses:
        "200":
          description: Effective rental policy.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RentalPolicyResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/StoreNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Unavailable"
  /catalog:
    get:
      operationId: listPublishedCatalog
      summary: List ecommerce-approved tenant products
      description: Requires the `catalog:read` scope.
      tags: [Catalog]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
        - $ref: "#/components/parameters/StoreId"
        - name: category_id
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: A cursor-paginated catalog page.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/StoreNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Unavailable"
  /inventory/availability:
    get:
      operationId: listInventoryAvailability
      summary: Read current or date-scoped rental availability
      description: >-
        Requires `inventory:read`. pickup_date and return_date must be provided
        together and cannot exceed the effective max_rental_days policy.
      tags: [Rental]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/After"
        - $ref: "#/components/parameters/StoreId"
        - name: skus
          in: query
          required: false
          description: Comma-separated list of at most 50 SKUs; cannot be combined with `after`.
          schema:
            type: string
            maxLength: 12799
          example: DR-100,DR-200
        - name: pickup_date
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: return_date
          in: query
          required: false
          schema:
            type: string
            format: date
      responses:
        "200":
          description: Current quantities and optional date-scoped counts.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AvailabilityResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/StoreNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Unavailable"
  /inventory/levels:
    put:
      operationId: setInventoryLevel
      summary: Compare and set one published SKU's current availability
      description: >-
        Requires `inventory:write`. The expected quantity is mandatory. The
        API may add synthetic physical units and may remove only units that a
        prior private API call created. It never deletes native POS garments,
        checked-out garments, or inventory owned by another inbound connector.
      tags: [Inventory]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            Stable unique key for this logical mutation. A successful replay
            with the same body returns the original result.
          schema:
            type: string
            minLength: 8
            maxLength: 120
            pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{7,119}$"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InventoryLevelWriteRequest"
            example:
              sku: SHOP-SKU-1
              store_id: 12
              available_quantity: 8
              expected_available_quantity: 6
      responses:
        "200":
          description: The level was set or a successful mutation was replayed.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryLevelWriteResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: >-
            The expected quantity is stale, the idempotency key was reused with
            another body, or protected inventory prevents the change.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "422":
          description: The tenant store or published SKU could not be resolved.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Unavailable"
  /inquiries:
    post:
      operationId: createWebsiteInquiry
      summary: Submit an inquiry for staff review
      description: >-
        Requires `inquiries:write`. This operation does not reserve inventory,
        create an order, or take payment. external_id is an idempotency key
        within the tenant.
      tags: [Inquiries]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InquiryRequest"
            examples:
              rentalInquiry:
                summary: Two-dress seven-day inquiry
                value:
                  external_id: website-inquiry-1042
                  customer_name: Example Customer
                  customer_email: customer@example.com
                  pickup_date: "2026-08-20"
                  return_date: "2026-08-27"
                  store_id: 12
                  requested_items:
                    - sku: DR-100
                      item_id: 9001
                      size: M
                    - sku: DR-200
                      size: "8"
                  message: Please confirm which option is available.
                  source: website
      responses:
        "200":
          description: Idempotent replay of an existing inquiry.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InquiryResponse"
        "201":
          description: Inquiry accepted for staff review.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InquiryResponse"
        "400":
          $ref: "#/components/responses/InvalidRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The external_id was already used with a different inquiry payload.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "422":
          $ref: "#/components/responses/StoreNotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/Unavailable"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: SleekSync API key
      description: >-
        Expiring ss_live_ key created by the designated administrator. Store it
        only in a server-side secret manager.
  headers:
    RequestId:
      description: Correlation ID for support and the metadata-only request audit.
      schema:
        type: string
        format: uuid
    RateLimitLimit:
      description: Maximum requests allowed for this key in the current minute.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests remaining for the key in the current minute.
      schema:
        type: integer
    RateLimitReset:
      description: Unix timestamp when the current rate-limit window resets.
      schema:
        type: integer
        format: int64
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    After:
      name: after
      in: query
      required: false
      description: SKU cursor returned as `pagination.next_cursor`.
      schema:
        type: string
        maxLength: 255
    StoreId:
      name: store_id
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
  responses:
    InvalidRequest:
      description: Query or payload validation failed.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    StoreNotFound:
      description: The requested store does not belong to the authenticated tenant.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: The key is missing, invalid, expired, or revoked.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: The tenant is disabled, the scope is missing, or a browser-origin request was rejected.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    PayloadTooLarge:
      description: The JSON body exceeds 64 KB.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: The per-key minute limit has been exceeded.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
        X-RateLimit-Limit:
          $ref: "#/components/headers/RateLimitLimit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/RateLimitRemaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/RateLimitReset"
        Retry-After:
          description: Seconds until another request should be attempted.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unavailable:
      description: A required service or data operation is unavailable.
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    HealthResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          type: object
          additionalProperties: false
          required: [status]
          properties:
            status:
              type: string
              const: ok
        request_id:
          type: string
          format: uuid
    RentalPolicyResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/RentalPolicy"
        request_id:
          type: string
          format: uuid
    RentalPolicy:
      type: object
      additionalProperties: false
      required:
        - store_id
        - default_rental_period
        - allow_extensions
        - max_extension_days
        - max_rental_days
        - max_requested_items
        - preparation_period
        - cleaning_period
        - late_return_fee
      properties:
        store_id:
          type: [integer, "null"]
          minimum: 1
        default_rental_period:
          type: integer
          minimum: 1
          maximum: 365
        allow_extensions:
          type: boolean
        max_extension_days:
          type: integer
          minimum: 0
          maximum: 365
        max_rental_days:
          type: integer
          minimum: 1
          maximum: 365
        max_requested_items:
          type: integer
          minimum: 1
          maximum: 3
        preparation_period:
          type: integer
          minimum: 0
          maximum: 365
        cleaning_period:
          type: integer
          minimum: 0
          maximum: 365
        late_return_fee:
          type: number
          minimum: 0
    CatalogResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CatalogItem"
        pagination:
          $ref: "#/components/schemas/Pagination"
        request_id:
          type: string
          format: uuid
    CatalogItem:
      type: object
      description: >-
        Customer-facing product data. Optional descriptive fields are nullable.
      required:
        - sku
        - product_name
        - ecommerce_featured
        - ecommerce_images
        - image_urls
        - available_sizes
        - prices
        - quantity
        - available_quantity
        - rented_quantity
        - store_ids
        - inventory_items
      properties:
        sku:
          type: string
        product_name:
          type: string
        brand_name:
          type: [string, "null"]
        color:
          type: [string, "null"]
        style:
          type: [string, "null"]
        material:
          type: [string, "null"]
        category_id:
          type: [integer, "null"]
        fit:
          type: [string, "null"]
        fabric:
          type: [string, "null"]
        pattern:
          type: [string, "null"]
        dress_code:
          type: [string, "null"]
        collection:
          type: [string, "null"]
        gender:
          type: [string, "null"]
        special_clothing_size:
          type: [string, "null"]
        closure_type:
          type: [string, "null"]
        sleeve_type:
          type: [string, "null"]
        care_instructions:
          type: [string, "null"]
        description:
          type: [string, "null"]
        deposit_amount_required:
          type: [number, "null"]
        ecommerce_slug:
          type: [string, "null"]
        ecommerce_featured:
          type: boolean
        ecommerce_images:
          type: array
          items: {}
        image_urls:
          type: array
          items:
            type: string
            format: uri
        available_sizes:
          type: array
          items:
            type: string
        size_unit:
          type: [string, "null"]
        prices:
          type: array
          items:
            $ref: "#/components/schemas/RetailPrice"
        quantity:
          type: integer
          minimum: 0
        available_quantity:
          type: integer
          minimum: 0
        rented_quantity:
          type: integer
          minimum: 0
        store_ids:
          type: array
          items:
            type: integer
        inventory_items:
          type: array
          items:
            $ref: "#/components/schemas/InventoryDescriptor"
        last_modified_at:
          type: [string, "null"]
          format: date-time
    RetailPrice:
      type: object
      additionalProperties: false
      required: [store_ids]
      properties:
        item_id:
          type: [integer, "null"]
        sku_size:
          type: [string, "null"]
        store_ids:
          type: array
          items:
            type: string
        retail_rental_price:
          type: [number, "null"]
        retail_purchase_price:
          type: [number, "null"]
    InventoryDescriptor:
      type: object
      additionalProperties: false
      required: [item_id, store_id, currently_available]
      properties:
        item_id:
          type: integer
        store_id:
          type: integer
        sku_size:
          type: [string, "null"]
        size_range:
          type: [string, "null"]
        garment_condition:
          type: [string, "null"]
        currently_available:
          type: boolean
    AvailabilityResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, date_scoped, request_id]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AvailabilityItem"
        pagination:
          $ref: "#/components/schemas/Pagination"
        date_scoped:
          type: boolean
        request_id:
          type: string
          format: uuid
    AvailabilityItem:
      type: object
      additionalProperties: false
      required:
        - sku
        - quantity
        - available_quantity
        - rented_quantity
        - available_for_dates
        - available_item_ids
        - store_ids
        - sizes
        - pickup_date
        - return_date
      properties:
        sku:
          type: string
        quantity:
          type: integer
          minimum: 0
        available_quantity:
          type: integer
          minimum: 0
        rented_quantity:
          type: integer
          minimum: 0
        available_for_dates:
          type: [integer, "null"]
          minimum: 0
        available_item_ids:
          type: array
          items:
            type: integer
        store_ids:
          type: array
          items:
            type: integer
        sizes:
          type: array
          items:
            type: string
        pickup_date:
          type: [string, "null"]
          format: date
        return_date:
          type: [string, "null"]
          format: date
        last_modified_at:
          type: [string, "null"]
          format: date-time
    InventoryLevelWriteRequest:
      type: object
      additionalProperties: false
      required:
        - sku
        - store_id
        - available_quantity
        - expected_available_quantity
      properties:
        sku:
          type: string
          minLength: 1
          maxLength: 50
        store_id:
          type: integer
          minimum: 1
        available_quantity:
          type: integer
          minimum: 0
          maximum: 5000
        expected_available_quantity:
          type: integer
          minimum: 0
          maximum: 5000
    InventoryLevelWriteResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          type: object
          additionalProperties: false
          required:
            - sku
            - store_id
            - previous_available_quantity
            - available_quantity
            - created_quantity
            - deleted_quantity
            - idempotent_replay
          properties:
            sku:
              type: string
            store_id:
              type: integer
              minimum: 1
            previous_available_quantity:
              type: integer
              minimum: 0
            available_quantity:
              type: integer
              minimum: 0
            created_quantity:
              type: integer
              minimum: 0
            deleted_quantity:
              type: integer
              minimum: 0
            idempotent_replay:
              type: boolean
        request_id:
          type: string
          format: uuid
    InquiryRequest:
      type: object
      additionalProperties: false
      required: [customer_name]
      dependentRequired:
        pickup_date: [return_date]
        return_date: [pickup_date]
      anyOf:
        - properties:
            customer_email:
              type: string
              format: email
              maxLength: 320
          required: [customer_email]
        - properties:
            customer_phone:
              type: string
              minLength: 1
              maxLength: 40
          required: [customer_phone]
      properties:
        external_id:
          type: string
          minLength: 1
          maxLength: 120
        customer_name:
          type: string
          minLength: 1
          maxLength: 200
        customer_email:
          type: string
          format: email
          maxLength: 320
        customer_phone:
          type: string
          minLength: 1
          maxLength: 40
        event_date:
          type: string
          format: date
        pickup_date:
          type: string
          format: date
        return_date:
          type: string
          format: date
        store_id:
          type: integer
          minimum: 1
        requested_items:
          type: array
          maxItems: 3
          items:
            $ref: "#/components/schemas/RequestedItem"
        message:
          type: string
          maxLength: 4000
        source:
          type: string
          minLength: 1
          maxLength: 100
        metadata:
          type: object
          description: JSON object limited to 8 KB by the API.
    RequestedItem:
      type: object
      additionalProperties: false
      required: [sku]
      properties:
        sku:
          type: string
          minLength: 1
          maxLength: 255
        item_id:
          type: integer
          minimum: 1
        size:
          type: string
          maxLength: 80
    InquiryResponse:
      type: object
      additionalProperties: false
      required: [data, idempotent_replay, request_id]
      properties:
        data:
          type: object
          additionalProperties: false
          required: [inquiry_id, status, created_at]
          properties:
            inquiry_id:
              type: string
              format: uuid
            external_id:
              type: [string, "null"]
            status:
              type: string
              enum: [new, reviewing, contacted, closed]
            created_at:
              type: string
              format: date-time
        idempotent_replay:
          type: boolean
        request_id:
          type: string
          format: uuid
    Pagination:
      type: object
      additionalProperties: false
      required: [limit, has_more, next_cursor]
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
        has_more:
          type: boolean
        next_cursor:
          type: [string, "null"]
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message, request_id]
          properties:
            code:
              type: string
            message:
              type: string
            request_id:
              type: string
              format: uuid
            details: {}
