openapi: 3.1.0
info:
  title: helpcode.ai API
  version: "1.0"
  summary: Public REST API for the helpcode.ai platform
  description: |
    The helpcode.ai REST API lets you create tickets, search your knowledge bases,
    manage items and devices, register webhooks and orchestrate the same data your
    teams see in the dashboard — programmatically.

    ## Authentication

    All endpoints under `/api/v1/*` are authenticated. Issue a key from the
    dashboard at **Settings → API Keys**, then send it in the `x-api-key` header
    on every request:

    ```bash
    curl https://helpcode.ai/api/v1/tickets \
      -H "x-api-key: hc_xxxx_xxxxxxxxxxxxxxxxxxxxxxxx"
    ```

    Keys are scoped to a single organisation. They are returned **only once at
    creation** — store them in your secrets manager. A revoked or expired key
    returns `401 Unauthorized`.

    ## Conventions

    - Base URL: `https://helpcode.ai/api/v1`
    - All requests and responses are JSON (`Content-Type: application/json`).
    - List endpoints accept `limit` (max 250) and `offset` query parameters and
      return `{ "data": [ ... ] }`.
    - Single-resource endpoints return `{ "data": { ... } }`.
    - Errors follow the H3 shape: `{ "statusCode": 400, "message": "..." }`.

    ## Rate limits

    Standard plans are limited to 600 requests per minute per key. Burst
    capacity and dedicated quotas are available on enterprise plans — contact
    `info@helpcode.ai`.
  contact:
    name: helpcode.ai support
    email: info@helpcode.ai
    url: https://helpcode.ai
  license:
    name: Proprietary
servers:
  - url: https://helpcode.ai/api/v1
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Tickets
    description: Create and follow up support tickets opened from any channel.
  - name: Knowledge Bases
    description: Manage knowledge bases, run vector search, sync connectors.
  - name: Items
    description: Track devices, machines and serial numbers your customers ask about.
  - name: Webhooks
    description: Subscribe to platform events with HMAC-signed deliveries.
  - name: API Keys
    description: Manage the API keys used to authenticate against this API.
  - name: Organization
    description: Read your organization profile and members.

paths:
  # ──────────────────────────────────────────────────────────────────────
  # Tickets
  # ──────────────────────────────────────────────────────────────────────
  /tickets:
    get:
      tags: [Tickets]
      summary: List tickets
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - in: query
          name: status
          schema:
            type: string
            enum: [open, in_progress, resolved, closed]
        - in: query
          name: priority
          schema:
            type: string
            enum: [low, normal, high, urgent]
        - in: query
          name: assigneeId
          schema: { type: string, format: uuid }
        - in: query
          name: channelId
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Tickets matching the filter
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Ticket" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Tickets]
      summary: Create a ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject, source]
              properties:
                subject: { type: string, maxLength: 500 }
                description: { type: string }
                priority:
                  type: string
                  enum: [low, normal, high, urgent]
                  default: normal
                source:
                  type: string
                  enum: [chat, troubleshooting, hotline, api, manual]
                kbId: { type: string, format: uuid }
                channelId: { type: string, format: uuid }
                assigneeId: { type: string, format: uuid }
                visitorEmail: { type: string, format: email }
                metadata: { type: object, additionalProperties: true }
            examples:
              minimal:
                summary: From an external API integration
                value:
                  subject: "Espresso machine — pump not priming"
                  description: "Customer reports no water flow after descaling cycle."
                  source: "api"
                  priority: "high"
                  visitorEmail: "alice@example.com"
      responses:
        "200":
          description: Ticket created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Ticket" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /tickets/{id}:
    parameters:
      - $ref: "#/components/parameters/TicketId"
    get:
      tags: [Tickets]
      summary: Get a ticket
      responses:
        "200":
          description: Ticket
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Ticket" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Tickets]
      summary: Update a ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [open, in_progress, resolved, closed]
                priority:
                  type: string
                  enum: [low, normal, high, urgent]
                assigneeId: { type: string, format: uuid, nullable: true }
                subject: { type: string }
                description: { type: string }
      responses:
        "200":
          description: Ticket updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Ticket" }

  /tickets/{id}/comments:
    parameters:
      - $ref: "#/components/parameters/TicketId"
    post:
      tags: [Tickets]
      summary: Add a comment to a ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body: { type: string }
                visibility:
                  type: string
                  enum: [public, internal]
                  default: public
      responses:
        "200":
          description: Comment created

  # ──────────────────────────────────────────────────────────────────────
  # Knowledge bases
  # ──────────────────────────────────────────────────────────────────────
  /knowledge-bases:
    get:
      tags: [Knowledge Bases]
      summary: List knowledge bases
      responses:
        "200":
          description: Knowledge bases in your organization
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/KnowledgeBase" }
    post:
      tags: [Knowledge Bases]
      summary: Create a knowledge base
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 255 }
                description: { type: string }
                language: { type: string, default: en }
                systemPrompt: { type: string }
                llmProvider:
                  type: string
                  enum: [openai, anthropic, google, mistral]
                  default: openai
                llmModel: { type: string, default: gpt-5.4-mini }
                embeddingProvider:
                  type: string
                  enum: [openai, anthropic, google, mistral]
                  default: openai
                embeddingModel: { type: string, default: text-embedding-3-small }
                temperature: { type: number, minimum: 0, maximum: 2, default: 0.7 }
      responses:
        "201":
          description: Knowledge base created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/KnowledgeBase" }

  /knowledge-bases/{id}:
    parameters:
      - $ref: "#/components/parameters/KbId"
    get:
      tags: [Knowledge Bases]
      summary: Get a knowledge base
      responses:
        "200":
          description: Knowledge base
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/KnowledgeBase" }
    patch:
      tags: [Knowledge Bases]
      summary: Update a knowledge base
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Knowledge base updated
    delete:
      tags: [Knowledge Bases]
      summary: Delete a knowledge base
      responses:
        "204":
          description: Deleted

  /knowledge-bases/{id}/search:
    parameters:
      - $ref: "#/components/parameters/KbId"
    post:
      tags: [Knowledge Bases]
      summary: Vector search
      description: |
        Run a semantic search against a knowledge base. Returns the top matching
        chunks with similarity scores and source citations.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [query]
              properties:
                query: { type: string, minLength: 1 }
                topK: { type: integer, minimum: 1, maximum: 50, default: 10 }
                minSimilarity: { type: number, minimum: 0, maximum: 1, default: 0.5 }
            examples:
              basic:
                value:
                  query: "How do I descale the espresso machine?"
                  topK: 5
      responses:
        "200":
          description: Ranked search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        documentId: { type: string, format: uuid }
                        chunk: { type: string }
                        similarity: { type: number }
                        page: { type: integer, nullable: true }

  /knowledge-bases/{id}/documents:
    parameters:
      - $ref: "#/components/parameters/KbId"
    get:
      tags: [Knowledge Bases]
      summary: List documents in a KB
      responses:
        "200":
          description: Documents
    post:
      tags: [Knowledge Bases]
      summary: Upload or register a document
      description: |
        Accepts a multipart upload (`file`) or a JSON body with a remote
        `sourceUrl`. Documents are processed asynchronously — poll the
        document status to know when ingestion completes.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: { type: string, format: binary }
                folderId: { type: string, format: uuid }
          application/json:
            schema:
              type: object
              properties:
                sourceUrl: { type: string, format: uri }
                title: { type: string }
                folderId: { type: string, format: uuid }
      responses:
        "201":
          description: Document accepted

  # ──────────────────────────────────────────────────────────────────────
  # Items
  # ──────────────────────────────────────────────────────────────────────
  /items:
    get:
      tags: [Items]
      summary: List items
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - in: query
          name: folderId
          schema: { type: string, format: uuid }
        - in: query
          name: search
          schema: { type: string }
      responses:
        "200":
          description: Items
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Item" }
    post:
      tags: [Items]
      summary: Create an item
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 500 }
                description: { type: string }
                serialNumber: { type: string }
                folderId: { type: string, format: uuid }
                metadata: { type: object, additionalProperties: true }
      responses:
        "200":
          description: Item created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Item" }

  /items/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    get:
      tags: [Items]
      summary: Get an item
      responses:
        "200":
          description: Item
    patch:
      tags: [Items]
      summary: Update an item
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Item updated
    delete:
      tags: [Items]
      summary: Delete an item
      responses:
        "204":
          description: Deleted

  # ──────────────────────────────────────────────────────────────────────
  # Webhooks
  # ──────────────────────────────────────────────────────────────────────
  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      responses:
        "200":
          description: Webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Webhook" }
    post:
      tags: [Webhooks]
      summary: Register a webhook
      description: |
        Registered webhooks receive POST callbacks whenever a subscribed event
        fires in your organization. The signing secret is returned **only on
        creation** — store it to verify the `x-helpcode-signature` header on
        each delivery.

        Available events include `ticket.created`, `ticket.updated`,
        `ticket.resolved`, `kb.document.processed`, `kb.gap.detected`,
        `session.handoff.requested`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                name: { type: string, maxLength: 200 }
                url: { type: string, format: uri }
                events:
                  type: array
                  minItems: 1
                  items: { type: string }
                kbId:
                  type: string
                  format: uuid
                  nullable: true
                  description: Scope this webhook to a single KB. Omit for org-wide events.
                isActive: { type: boolean, default: true }
            examples:
              ticketEvents:
                value:
                  name: "CRM sync"
                  url: "https://example.com/webhooks/helpcode"
                  events: ["ticket.created", "ticket.resolved"]
      responses:
        "200":
          description: Webhook created (with one-time secret)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/Webhook"
                      - type: object
                        properties:
                          secret: { type: string, description: "Visible once. Store it now." }

  /webhooks/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    delete:
      tags: [Webhooks]
      summary: Delete a webhook
      responses:
        "204":
          description: Deleted

  # ──────────────────────────────────────────────────────────────────────
  # API keys
  # ──────────────────────────────────────────────────────────────────────
  /api-keys:
    get:
      tags: [API Keys]
      summary: List API keys
      description: Returns metadata for each key. The raw key value is never returned again after creation.
      responses:
        "200":
          description: Keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ApiKey" }
    post:
      tags: [API Keys]
      summary: Create an API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 255 }
                permissions:
                  type: array
                  items: { type: string }
                expiresAt: { type: string, format: date-time }
      responses:
        "201":
          description: Key created (raw value returned once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: "#/components/schemas/ApiKey"
                      - type: object
                        properties:
                          key:
                            type: string
                            description: The raw API key. Visible once. Store it now.
                            example: hc_pNnT0ev3_QuhlUH80S9qVZk6T1vBvHo3jYPXa3FRvN0Y

  /api-keys/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    delete:
      tags: [API Keys]
      summary: Revoke an API key
      responses:
        "204":
          description: Revoked

  # ──────────────────────────────────────────────────────────────────────
  # Organization
  # ──────────────────────────────────────────────────────────────────────
  /organization:
    get:
      tags: [Organization]
      summary: Get the current organization
      responses:
        "200":
          description: Organization
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Organization" }

  /organization/members:
    get:
      tags: [Organization]
      summary: List organization members
      responses:
        "200":
          description: Members

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: |
        Issue an API key from **Dashboard → Settings → API Keys**. Send it on
        every request as `x-api-key: hc_<prefix>_<secret>`. Keys are returned
        only once at creation.

  parameters:
    Limit:
      in: query
      name: limit
      schema: { type: integer, minimum: 1, maximum: 250, default: 50 }
    Offset:
      in: query
      name: offset
      schema: { type: integer, minimum: 0, default: 0 }
    TicketId:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }
    KbId:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    Error:
      type: object
      properties:
        statusCode: { type: integer }
        message: { type: string }
      required: [statusCode, message]
      example:
        statusCode: 401
        message: "Authentication required. Provide an API key via x-api-key header or sign in."

    Ticket:
      type: object
      properties:
        id: { type: string, format: uuid }
        orgId: { type: string, format: uuid }
        subject: { type: string }
        description: { type: string, nullable: true }
        status:
          type: string
          enum: [open, in_progress, resolved, closed]
        priority:
          type: string
          enum: [low, normal, high, urgent]
        source:
          type: string
          enum: [chat, troubleshooting, hotline, api, manual]
        kbId: { type: string, format: uuid, nullable: true }
        channelId: { type: string, format: uuid, nullable: true }
        assigneeId: { type: string, format: uuid, nullable: true }
        sessionId: { type: string, format: uuid, nullable: true }
        visitorEmail: { type: string, format: email, nullable: true }
        metadata: { type: object, additionalProperties: true, nullable: true }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    KnowledgeBase:
      type: object
      properties:
        id: { type: string, format: uuid }
        orgId: { type: string, format: uuid }
        name: { type: string }
        description: { type: string, nullable: true }
        language: { type: string }
        llmProvider: { type: string }
        llmModel: { type: string }
        embeddingProvider: { type: string }
        embeddingModel: { type: string }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }

    Item:
      type: object
      properties:
        id: { type: string, format: uuid }
        orgId: { type: string, format: uuid }
        name: { type: string }
        description: { type: string, nullable: true }
        serialNumber: { type: string, nullable: true }
        folderId: { type: string, format: uuid, nullable: true }
        metadata: { type: object, additionalProperties: true, nullable: true }
        createdAt: { type: string, format: date-time }

    Webhook:
      type: object
      properties:
        id: { type: string, format: uuid }
        orgId: { type: string, format: uuid }
        kbId: { type: string, format: uuid, nullable: true }
        name: { type: string, nullable: true }
        url: { type: string, format: uri }
        events:
          type: array
          items: { type: string }
        isActive: { type: boolean }
        createdAt: { type: string, format: date-time }

    ApiKey:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        keyPrefix: { type: string, example: "hc_pNnT0ev3" }
        permissions:
          type: array
          items: { type: string }
          nullable: true
        lastUsedAt: { type: string, format: date-time, nullable: true }
        expiresAt: { type: string, format: date-time, nullable: true }
        revokedAt: { type: string, format: date-time, nullable: true }
        createdAt: { type: string, format: date-time }

    Organization:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }
        createdAt: { type: string, format: date-time }
