openapi: 3.0.3
info:
  title: notifito Edge API
  description: |
    Public API for widget integration and availability push.
    
    ## Authentication
    
    notifito uses two types of API keys:
    - **Publishable keys** (`pk_`): Used for widget endpoints, passed as query parameters
    - **Secret keys** (`sk_`): Used for push API endpoints, passed as Bearer tokens
    
    ## Rate Limiting
    
    All endpoints are rate-limited per API key. See endpoint documentation for specific limits.
    
    ## Support
    
    Contact: turker@gmail.com
  version: 1.0.0
  contact:
    name: notifito Support
    email: turker@gmail.com
    url: https://github.com/turkerince/notifito
  license:
    name: Proprietary

servers:
  - url: https://edge.notifito.com
    description: Production
  - url: http://127.0.0.1:8002
    description: Local development

tags:
  - name: Widget
    description: Widget integration endpoints
  - name: Push
    description: Availability push endpoints
  - name: Consent
    description: Confirmation and unsubscribe pages

paths:
  /v1/widget/schema:
    get:
      tags:
        - Widget
      summary: Get widget schema
      description: |
        Returns the attribute schema for rendering the subscription form.
        The schema includes all attribute definitions with their types, options, and validation rules.
      operationId: getWidgetSchema
      parameters:
        - name: key
          in: query
          required: true
          description: Publishable API key
          schema:
            type: string
            pattern: '^pk_live_'
            example: pk_live_ADbaCjvo5TT1x8104zHXKSHr
        - name: item_type
          in: query
          required: true
          description: Item type slug
          schema:
            type: string
            example: sneaker
      responses:
        '200':
          description: Schema returned successfully
          headers:
            Cache-Control:
              schema:
                type: string
                example: public, max-age=300
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WidgetSchema'
              example:
                tenant_name: Sneaker Store
                item_type:
                  id: 01a04f92-c67c-7093-88c8-28f1b313242c
                  name: Sneaker
                  slug: sneaker
                  schema_version: 1
                  attribute_schema:
                    - key: size
                      label: Size
                      type: enum
                      options: ["40", "41", "42", "43"]
                      required: true
                      matchable: true
                      multiple: false
                items:
                  - id: 01a04f92-c68e-7333-b731-c691115aed23
                    name: Air Force 1
                    external_ref: SKU-AF1
        '403':
          description: Origin not allowed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Origin not allowed.
        '404':
          description: Item type not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Unknown item type.
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/widget/subscriptions:
    post:
      tags:
        - Widget
      summary: Create subscription
      description: |
        Submits a new subscription request. Triggers the double opt-in flow.
        
        The consumer will receive a confirmation email with a link to verify their email address.
        Subscriptions expire after 72 hours if not confirmed.
      operationId: createSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubscriptionRequest'
            example:
              key: pk_live_ADbaCjvo5TT1x8104zHXKSHr
              item_type: sneaker
              attributes:
                size: "42"
              contact_value: consumer@example.com
      responses:
        '202':
          description: Subscription created, confirmation email sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubscriptionResponse'
              example:
                subscription_id: sub-123
                status: pending_confirmation
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                message: The given data was invalid.
                errors:
                  attributes.size:
                    - "Size must be one of: 40, 41, 42, 43."
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /v1/availability:
    post:
      tags:
        - Push
      summary: Push availability
      description: |
        Signals that items are now available. Triggers matching and notification.
        
        Use idempotency keys to prevent duplicate notifications from retries.
        The same idempotency key will return the original event without processing again.
      operationId: pushAvailability
      parameters:
        - name: Authorization
          in: header
          required: true
          description: Secret API key as Bearer token
          schema:
            type: string
            pattern: '^Bearer sk_live_'
            example: Bearer sk_live_z0SWNgZfrojI6ycZwvxjcUHl
        - name: Idempotency-Key
          in: header
          required: true
          description: Unique request ID for deduplication
          schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AvailabilityRequest'
            example:
              item_type: sneaker
              attributes:
                size: "42"
              capacity: 3
      responses:
        '202':
          description: Availability event recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AvailabilityResponse'
              examples:
                newEvent:
                  summary: New event
                  value:
                    event_id: evt-123
                    duplicate: false
                duplicate:
                  summary: Duplicate event
                  value:
                    event_id: evt-123
                    duplicate: true
        '401':
          description: Unauthorized (invalid key)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                message: The given data was invalid.
                errors:
                  attributes.size:
                    - "Size must be one of: 40, 41, 42, 43."
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /c/{token}:
    get:
      tags:
        - Consent
      summary: Confirm subscription
      description: Confirms a subscription via token link. No authentication required.
      operationId: confirmSubscription
      parameters:
        - name: token
          in: path
          required: true
          description: Confirmation token (32 alphanumeric characters)
          schema:
            type: string
            pattern: '^[A-Za-z0-9]{32}$'
      responses:
        '200':
          description: Confirmation page
          content:
            text/html:
              schema:
                type: string
        '404':
          description: Invalid or expired token
        '410':
          description: Token already used

  /u/{token}:
    get:
      tags:
        - Consent
      summary: Unsubscribe
      description: Unsubscribes via token link. No authentication required.
      operationId: unsubscribe
      parameters:
        - name: token
          in: path
          required: true
          description: Unsubscribe token (32 alphanumeric characters)
          schema:
            type: string
            pattern: '^[A-Za-z0-9]{32}$'
      responses:
        '200':
          description: Unsubscribe confirmation page
          content:
            text/html:
              schema:
                type: string
        '404':
          description: Invalid token
    post:
      tags:
        - Consent
      summary: Unsubscribe (POST)
      description: |
        Unsubscribes via POST request. Supports List-Unsubscribe-Post header.
        No authentication required.
      operationId: unsubscribePost
      parameters:
        - name: token
          in: path
          required: true
          description: Unsubscribe token (32 alphanumeric characters)
          schema:
            type: string
            pattern: '^[A-Za-z0-9]{32}$'
      responses:
        '200':
          description: Unsubscribe confirmation page
          content:
            text/html:
              schema:
                type: string
        '404':
          description: Invalid token

components:
  securitySchemes:
    publishableKey:
      type: apiKey
      in: query
      name: key
      description: Publishable API key (pk_live_xxx)
    secretKey:
      type: http
      scheme: bearer
      description: Secret API key as Bearer token (sk_live_xxx)

  schemas:
    WidgetSchema:
      type: object
      required:
        - tenant_name
        - item_type
        - items
      properties:
        tenant_name:
          type: string
          description: Seller's display name
          example: Sneaker Store
        item_type:
          $ref: '#/components/schemas/ItemTypeSchema'
        items:
          type: array
          description: Available items of this type
          items:
            $ref: '#/components/schemas/ItemSchema'

    ItemTypeSchema:
      type: object
      required:
        - id
        - name
        - slug
        - schema_version
        - attribute_schema
      properties:
        id:
          type: string
          format: uuid
          description: Item type unique identifier
        name:
          type: string
          description: Item type display name
          example: Sneaker
        slug:
          type: string
          description: URL-friendly identifier
          example: sneaker
        schema_version:
          type: integer
          description: Current schema version
          example: 1
        attribute_schema:
          type: array
          description: Ordered list of attribute definitions
          items:
            $ref: '#/components/schemas/AttributeDefinition'

    ItemSchema:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
          description: Item unique identifier
        name:
          type: string
          description: Item display name
          example: Air Force 1
        external_ref:
          type: string
          nullable: true
          description: External reference (e.g., SKU)
          example: SKU-AF1

    AttributeDefinition:
      type: object
      required:
        - key
        - label
        - type
        - required
        - matchable
        - multiple
      properties:
        key:
          type: string
          description: Attribute key (lowercase snake_case)
          pattern: '^[a-z][a-z0-9_]*$'
          example: size
        label:
          type: string
          description: Display label
          example: Size
        type:
          type: string
          enum:
            - enum
            - integer
            - decimal
            - date
            - date_range
            - boolean
            - text
          description: Attribute type
        options:
          type: array
          description: Available options (for enum type)
          items:
            type: string
          example: ["40", "41", "42", "43"]
        required:
          type: boolean
          description: Whether attribute is required
          example: true
        matchable:
          type: boolean
          description: Whether attribute participates in matching
          example: true
        multiple:
          type: boolean
          description: Whether multiple values can be selected (enum only)
          example: false

    SubscriptionRequest:
      type: object
      required:
        - key
        - item_type
        - contact_value
      properties:
        key:
          type: string
          description: Publishable API key
          pattern: '^pk_live_'
          example: pk_live_ADbaCjvo5TT1x8104zHXKSHr
        item_type:
          type: string
          description: Item type slug
          example: sneaker
        item_id:
          type: string
          format: uuid
          nullable: true
          description: Specific item UUID (optional)
        attributes:
          type: object
          description: Subscription attributes
          additionalProperties: true
          example:
            size: "42"
        contact_value:
          type: string
          format: email
          description: Consumer's email address
          example: consumer@example.com

    SubscriptionResponse:
      type: object
      required:
        - subscription_id
        - status
      properties:
        subscription_id:
          type: string
          format: uuid
          description: Created subscription ID
        status:
          type: string
          enum:
            - pending_confirmation
          description: Subscription status

    AvailabilityRequest:
      type: object
      required:
        - item_type
      properties:
        item_type:
          type: string
          description: Item type slug
          example: sneaker
        item_id:
          type: string
          format: uuid
          nullable: true
          description: Specific item UUID (optional)
        attributes:
          type: object
          description: Event attributes (wildcard if omitted)
          additionalProperties: true
          example:
            size: "42"
        capacity:
          type: integer
          nullable: true
          description: Available capacity
          example: 3
        available_from:
          type: string
          format: date-time
          nullable: true
          description: Availability window start (ISO 8601)
        available_until:
          type: string
          format: date-time
          nullable: true
          description: Availability window end (ISO 8601)

    AvailabilityResponse:
      type: object
      required:
        - event_id
        - duplicate
      properties:
        event_id:
          type: string
          format: uuid
          description: Created event ID
        duplicate:
          type: boolean
          description: Whether this was a duplicate request

    ErrorResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: Human-readable error message

    ValidationErrorResponse:
      type: object
      required:
        - message
        - errors
      properties:
        message:
          type: string
          description: Human-readable error message
        errors:
          type: object
          description: Field-specific validation errors
          additionalProperties:
            type: array
            items:
              type: string

security:
  - publishableKey: []
