openapi: 3.1.0
info:
  title: AfterCheck API v1
  version: 1.0.0-rc
  summary: Pre-release documentation for the implemented AfterCheck API v1 endpoints.
  description: |
    AfterCheck API v1 gives server-side systems a small feedback-first
    integration surface: list campaigns and locations, manage tracked feedback
    requests, read organization-scoped responses, inspect next actions, prepare
    webhook endpoints, and connect Zapier REST Hook subscriptions.

    This OpenAPI file documents only endpoints currently implemented in the
    AfterCheck repository. It is the source of truth for the Scalar-rendered
    public reference at /api-docs.

    The v1 API contract is the current pre-1.0 integration surface for the
    `/api/v1` runtime routes. It should not be treated as a stable 1.0 public
    contract until broader product, support, billing, and operations contracts
    are accepted.

    Return to the [AfterCheck marketing site](/) or the [API overview](/#api).
  x-logo:
    url: /brand/aftercheck-logo.svg
    altText: AfterCheck
servers:
  - url: http://localhost:3000
    description: Local development
tags:
  - name: Authentication
    description: API key connection testing.
  - name: Campaigns
    description: Reusable feedback flows that can be shared through public campaign links.
  - name: Feedback requests
    description: Tracked customer, job, order, appointment, or ticket-specific feedback invitations.
  - name: Responses
    description: Submitted feedback records scoped to the API key organization.
  - name: Action destinations
    description: Optional neutral next actions shown after feedback.
  - name: Locations
    description: Workspace location metadata for approved integrations.
  - name: Webhook endpoints
    description: Endpoint and subscription management for outbound events.
  - name: Zapier
    description: Zapier REST Hook subscription helpers.
security:
  - bearerApiKey: []
paths:
  /api/v1/me:
    get:
      operationId: getApiKeyConnection
      summary: Test API key connection
      description: Test API key authentication and return organization metadata without requiring campaigns or responses to exist.
      tags:
        - Authentication
      security:
        - bearerApiKey: []
      responses:
        "200":
          description: API key connection metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyConnection"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/action-destinations:
    get:
      operationId: listActionDestinations
      summary: List action destinations
      description: List neutral next-action destinations for the API key organization.
      tags:
        - Action destinations
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Action destination list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ActionDestination"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/campaigns:
    get:
      operationId: listCampaigns
      summary: List campaigns
      description: List campaigns for the API key organization.
      tags:
        - Campaigns
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - draft
              - active
              - paused
              - archived
      responses:
        "200":
          description: Campaign list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Campaign"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/feedback-requests:
    get:
      operationId: listFeedbackRequests
      summary: List feedback requests
      description: List tracked feedback requests and generated request links for the API key organization.
      tags:
        - Feedback requests
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Feedback request list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/FeedbackRequest"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createFeedbackRequest
      summary: Create feedback request
      description: Create a tracked feedback request and public request-token link for the API key organization. Optional idempotency keys are scoped to the organization, API key, route, method, and request payload.
      tags:
        - Feedback requests
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFeedbackRequest"
      responses:
        "201":
          description: Feedback request created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackRequestCreated"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Duplicate external reference or idempotency-key payload mismatch
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/locations:
    get:
      operationId: listLocations
      summary: List locations
      description: List workspace locations for the API key organization.
      tags:
        - Locations
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Location list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Location"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/responses:
    get:
      operationId: listResponses
      summary: List responses
      description: List feedback responses for the API key organization.
      tags:
        - Responses
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - name: sentiment
          in: query
          required: false
          schema:
            type: string
            enum:
              - positive
              - neutral
              - negative
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - new
              - acknowledged
              - contacted
              - resolved
              - closed
              - do_not_contact
        - name: campaign_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
        - name: feedback_request_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
        - name: created_after
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: sample
          in: query
          required: false
          description: Return a stable non-customer sample row for connector trigger testing.
          schema:
            type: boolean
      responses:
        "200":
          description: Response list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/FeedbackResponse"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/webhook-endpoints:
    get:
      operationId: listWebhookEndpoints
      summary: List webhook endpoints
      description: List webhook endpoints and subscribed events for the API key organization.
      tags:
        - Webhook endpoints
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Webhook endpoint list
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - total
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookEndpoint"
                  total:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
    post:
      operationId: createWebhookEndpoint
      summary: Create webhook endpoint
      description: Create a webhook endpoint record and event subscriptions. Live delivery still requires the server delivery gate and provider configuration.
      tags:
        - Webhook endpoints
      security:
        - bearerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookEndpoint"
      responses:
        "201":
          description: Webhook endpoint created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: Duplicate webhook endpoint URL
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/zapier/subscriptions:
    post:
      operationId: createZapierSubscription
      summary: Subscribe Zapier REST Hook
      description: Subscribe or reactivate a Zapier target URL for one AfterCheck webhook event.
      tags:
        - Zapier
      security:
        - bearerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ZapierSubscriptionCreate"
      responses:
        "200":
          description: Existing Zapier subscription updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ZapierSubscription"
        "201":
          description: Zapier subscription created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ZapierSubscription"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/zapier/responses:
    get:
      operationId: listZapierPollingResponses
      summary: List responses for Zapier polling
      description: List feedback responses as a root JSON array for Zapier polling triggers.
      tags:
        - Zapier
      security:
        - bearerApiKey: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - name: sentiment
          in: query
          required: false
          schema:
            type: string
            enum:
              - positive
              - neutral
              - negative
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - new
              - acknowledged
              - contacted
              - resolved
              - closed
              - do_not_contact
        - name: campaign_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
        - name: feedback_request_id
          in: query
          required: false
          schema:
            type: string
            format: uuid
        - name: created_after
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: sample
          in: query
          required: false
          description: Return a stable non-customer sample row for connector trigger testing.
          schema:
            type: boolean
      responses:
        "200":
          description: Zapier polling response list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/FeedbackResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /api/v1/zapier/subscriptions/{subscriptionId}:
    delete:
      operationId: deleteZapierSubscription
      summary: Unsubscribe Zapier REST Hook
      description: Deactivate a Zapier webhook endpoint subscription by the id returned from subscribe.
      tags:
        - Zapier
      security:
        - bearerApiKey: []
      parameters:
        - name: subscriptionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Zapier subscription deactivated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ZapierSubscription"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/ServerError"
components:
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        default: 50
        maximum: 100
        minimum: 0
    Offset:
      name: offset
      in: query
      required: false
      schema:
        type: integer
        default: 0
        minimum: 0
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Optional customer-generated key for safe retries. Use 8-160 letters, numbers, dots, underscores, colons, or hyphens. Matching retries replay the original feedback-request response and include x-idempotent-replay.
      schema:
        type: string
        minLength: 8
        maxLength: 160
        pattern: "^[A-Za-z0-9._:-]+$"
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
      description: Dashboard-created AfterCheck API key.
  responses:
    BadRequest:
      description: Invalid request input
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing, invalid, revoked, or organization-blocked API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: Missing required scope
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource not found or not accessible to the organization
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ServerError:
      description: Unexpected server-side failure
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    RateLimited:
      description: API rate limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    ApiKeyConnection:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - api_key
            - organization
          properties:
            api_key:
              type: object
              required:
                - id
                - prefix
                - scopes
              properties:
                id:
                  type: string
                  format: uuid
                prefix:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
            organization:
              type: object
              required:
                - id
                - name
                - slug
              properties:
                id:
                  type: string
                  format: uuid
                name:
                  type:
                    - string
                    - "null"
                slug:
                  type:
                    - string
                    - "null"
    ActionDestination:
      type: object
      required:
        - id
        - label
        - type
        - url
        - status
        - sort_order
        - click_count
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type:
            - string
            - "null"
          format: uuid
        location_id:
          type:
            - string
            - "null"
          format: uuid
        label:
          type: string
        type:
          type: string
        url:
          type: string
          format: uri
        status:
          type: string
        sort_order:
          type: integer
        click_count:
          type: integer
        last_clicked_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    Campaign:
      type: object
      required:
        - id
        - name
        - slug
        - status
        - type
        - headline
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status:
          type: string
        type:
          type: string
        headline:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreateFeedbackRequest:
      type: object
      required:
        - campaign_id
      properties:
        campaign_id:
          type: string
          format: uuid
        customer_name:
          type: string
          maxLength: 200
        customer_email:
          type: string
          format: email
          maxLength: 320
        external_ref:
          type: string
          maxLength: 500
        source:
          type: string
          maxLength: 80
          description: Delivery/source label for the request link.
        utm_source:
          type: string
          maxLength: 120
        utm_medium:
          type: string
          maxLength: 120
        utm_campaign:
          type: string
          maxLength: 120
        utm_content:
          type: string
          maxLength: 120
        utm_term:
          type: string
          maxLength: 120
    FeedbackRequestCreated:
      type: object
      required:
        - id
        - campaign_id
        - request_token
        - request_url
        - campaign_slug
        - url
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        request_token:
          type: string
        request_url:
          type: string
          format: uri
        campaign_slug:
          type: string
        customer_name:
          type:
            - string
            - "null"
        customer_email:
          type:
            - string
            - "null"
          format: email
        external_ref:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        utm_source:
          type:
            - string
            - "null"
        utm_medium:
          type:
            - string
            - "null"
        utm_campaign:
          type:
            - string
            - "null"
        utm_content:
          type:
            - string
            - "null"
        utm_term:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        url:
          type: string
          format: uri
    FeedbackRequest:
      allOf:
        - $ref: "#/components/schemas/FeedbackRequestCreated"
        - type: object
          properties:
            location_id:
              type:
                - string
                - "null"
              format: uuid
            send_status:
              type: string
            responded_at:
              type:
                - string
                - "null"
              format: date-time
            expires_at:
              type:
                - string
                - "null"
              format: date-time
            updated_at:
              type: string
              format: date-time
    FeedbackResponse:
      type: object
      required:
        - id
        - campaign_id
        - feedback_request_id
        - sentiment
        - status
        - category
        - comment
        - customer_name
        - source
        - external_ref
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        campaign_id:
          type: string
          format: uuid
        feedback_request_id:
          type:
            - string
            - "null"
          format: uuid
        sentiment:
          type: string
          enum:
            - positive
            - neutral
            - negative
        status:
          type: string
        category:
          type:
            - string
            - "null"
        comment:
          type:
            - string
            - "null"
        customer_name:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        external_ref:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Location:
      type: object
      required:
        - id
        - name
        - status
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
        address_line1:
          type:
            - string
            - "null"
        address_line2:
          type:
            - string
            - "null"
        city:
          type:
            - string
            - "null"
        region:
          type:
            - string
            - "null"
        postal_code:
          type:
            - string
            - "null"
        country:
          type:
            - string
            - "null"
        phone:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
          format: email
        website_url:
          type:
            - string
            - "null"
          format: uri
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CreateWebhookEndpoint:
      type: object
      required:
        - name
        - endpoint_url
        - subscribed_events
      properties:
        name:
          type: string
          maxLength: 160
        endpoint_url:
          type: string
          format: uri
        status:
          type: string
          enum:
            - active
            - inactive
            - planned
          default: inactive
        subscribed_events:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/WebhookEventType"
    WebhookEndpoint:
      type: object
      required:
        - id
        - name
        - endpoint_url
        - status
        - provider
        - subscribed_events
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        endpoint_url:
          type: string
          format: uri
        status:
          type: string
        provider:
          type: string
        subscribed_events:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        last_delivery_state:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WebhookEventType:
      type: string
      enum:
        - response.created
        - response.needs_attention
        - response.sentiment_changed
        - response.status_changed
        - feedback_request.created
        - feedback_request.responded
        - feedback_request.expired
        - action_destination.clicked
    ZapierSubscriptionCreate:
      type: object
      required:
        - target_url
      properties:
        target_url:
          type: string
          format: uri
        targetUrl:
          type: string
          format: uri
          description: Alternative field accepted for Zapier bundle.targetUrl payloads.
        event_type:
          $ref: "#/components/schemas/WebhookEventType"
        name:
          type: string
          maxLength: 160
    ZapierSubscription:
      type: object
      required:
        - id
        - endpoint_id
        - target_url
        - event_type
        - subscribed_events
        - status
      properties:
        id:
          type: string
          format: uuid
        endpoint_id:
          type: string
          format: uuid
        target_url:
          type: string
          format: uri
        event_type:
          $ref: "#/components/schemas/WebhookEventType"
        subscribed_events:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        status:
          type: string
