openapi: 3.1.0
info:
  title: Campfire Partner API
  version: '2026-07-23'
  description: |
    Customer-owned WhatsApp connection, durable messaging, embedded support surfaces,
    and delivery diagnostics for Campfire platform partners. All IDs are opaque prefixed IDs.
servers:
  - url: https://api.campfire.is/api/partners/v2
security:
  - partnerBearer: []
paths:
  /workspaces:
    post:
      summary: Create or retrieve a managed workspace for an external tenant
      operationId: createWorkspace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorkspaceRequest'
      responses:
        '201': { description: Workspace created, content: { application/json: { schema: { $ref: '#/components/schemas/WorkspaceCreated' } } } }
        '200': { description: Existing workspace returned, content: { application/json: { schema: { $ref: '#/components/schemas/WorkspaceCreated' } } } }
        '400': { $ref: '#/components/responses/Error' }
        '401': { $ref: '#/components/responses/Error' }
    get:
      summary: List the caller's managed workspaces
      operationId: listWorkspaces
      responses:
        '200': { description: Workspace summaries }
  /workspaces/{workspaceId}:
    get:
      summary: Read workspace policy and installation state
      operationId: getWorkspace
      parameters: [ { $ref: '#/components/parameters/WorkspaceId' } ]
      responses:
        '200': { description: Workspace summary }
        '404': { $ref: '#/components/responses/Error' }
  /workspaces/{workspaceId}/commands:
    post:
      summary: Accept an idempotent outbound messaging command
      operationId: createCommand
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: Idempotency-Key
          in: header
          required: true
          schema: { type: string, minLength: 8, maxLength: 128 }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/MessageCommand' }
      responses:
        '202': { description: Command accepted }
        '200': { description: Idempotent command replay }
        '409': { $ref: '#/components/responses/Error' }
        '422': { $ref: '#/components/responses/Error' }
  /workspaces/{workspaceId}/installation:
    get:
      summary: Read current WhatsApp installation health
      operationId: getInstallation
      parameters: [ { $ref: '#/components/parameters/WorkspaceId' } ]
      responses:
        '200': { description: Installation state }
  /workspaces/{workspaceId}/setup-links:
    post:
      summary: Create an expiring customer-owned WhatsApp setup link
      operationId: createSetupLink
      parameters: [ { $ref: '#/components/parameters/WorkspaceId' } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateSetupLinkRequest' }
      responses:
        '201': { description: Setup link created. The bearer URL is returned only here., content: { application/json: { schema: { $ref: '#/components/schemas/SetupLink' } } } }
        '403': { $ref: '#/components/responses/Error' }
    get:
      summary: List setup-link metadata without reissuing bearer URLs
      operationId: listSetupLinks
      parameters: [ { $ref: '#/components/parameters/WorkspaceId' } ]
      responses:
        '200': { description: Setup-link summaries }
  /workspaces/{workspaceId}/setup-links/{setupLinkId}:
    delete:
      summary: Revoke an uncompleted setup link
      operationId: revokeSetupLink
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: setupLinkId
          in: path
          required: true
          schema: { type: string, pattern: '^sl_' }
      responses:
        '200': { description: Link revoked }
        '404': { $ref: '#/components/responses/Error' }
        '409': { $ref: '#/components/responses/Error' }
  /workspaces/{workspaceId}/events:
    get:
      summary: Pull ordered durable partner events for reconciliation
      operationId: pullEvents
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: afterSequence
          in: query
          schema: { type: integer, minimum: 0, default: 0 }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500, default: 100 }
      responses:
        '200': { description: Ordered event envelope list }
  /surface-sessions:
    post:
      summary: Issue a short-lived scoped session for the embedded inbox surface
      operationId: createSurfaceSession
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SurfaceSessionRequest' }
      responses:
        '201': { description: Surface session issued }
  /workspaces/{workspaceId}/messages/{messageId}/timeline:
    get:
      summary: Read command, queue, Meta receipt, and partner-webhook delivery evidence
      operationId: getMessageTimeline
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: messageId
          in: path
          required: true
          schema: { type: string, pattern: '^msg_' }
      responses:
        '200': { description: Delivery timeline, content: { application/json: { schema: { $ref: '#/components/schemas/MessageTimeline' } } } }
        '404': { $ref: '#/components/responses/Error' }
  /workspaces/{workspaceId}/logs:
    get:
      summary: Find a delivery timeline by Meta provider message ID (wamid)
      operationId: searchDeliveryLogs
      parameters:
        - $ref: '#/components/parameters/WorkspaceId'
        - name: wamid
          in: query
          required: true
          schema: { type: string, minLength: 1, maxLength: 512 }
      responses:
        '200': { description: Delivery timeline, content: { application/json: { schema: { $ref: '#/components/schemas/MessageTimeline' } } } }
        '404': { $ref: '#/components/responses/Error' }
components:
  securitySchemes:
    partnerBearer:
      type: http
      scheme: bearer
      bearerFormat: Campfire partner API key
  parameters:
    WorkspaceId:
      name: workspaceId
      in: path
      required: true
      schema: { type: string, pattern: '^mw_' }
  responses:
    Error:
      description: Versioned error envelope
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
  schemas:
    CreateWorkspaceRequest:
      type: object
      required: [externalTenantId, name]
      properties:
        externalTenantId: { type: string, maxLength: 128 }
        name: { type: string, maxLength: 200 }
        locale: { type: string }
        timezone: { type: string }
        returnUrlAllowlist:
          type: array
          items: { type: string, format: uri }
          maxItems: 20
    WorkspaceCreated:
      type: object
      required: [workspaceId, externalTenantId, created]
      properties:
        workspaceId: { type: string, pattern: '^mw_' }
        externalTenantId: { type: string }
        created: { type: boolean }
    CreateSetupLinkRequest:
      type: object
      required: [externalTenantId, redirectUrl]
      properties:
        externalTenantId: { type: string }
        redirectUrl: { type: string, format: uri }
        allowedConnectionTypes:
          type: array
          items: { type: string, enum: [whatsapp] }
          default: [whatsapp]
        expiresInHours: { type: integer, minimum: 1, maximum: 168, default: 24 }
    SetupLink:
      type: object
      required: [setupLinkId, externalTenantId, status, url, expiresAt]
      properties:
        setupLinkId: { type: string, pattern: '^sl_' }
        externalTenantId: { type: string }
        status: { type: string, enum: [active, opened, connected, failed, revoked, expired] }
        url: { type: string, format: uri, description: Returned only from create; secret is a URL fragment. }
        expiresAt: { type: string, format: date-time }
    MessageCommand:
      type: object
      required: [type, payload]
      properties:
        type: { type: string, enum: [message.send] }
        correlationId: { type: string }
        payload:
          type: object
          required: [senderAttribution, content]
          properties:
            conversationId: { type: string, pattern: '^conv_' }
            contact: { type: object, properties: { phoneE164: { type: string }, name: { type: string } } }
            senderAttribution: { type: string, enum: [automation, system] }
            content: { type: object, description: Text, approved template alias, media, or interactive payload. }
    SurfaceSessionRequest:
      type: object
      required: [externalTenantId, externalUserId, capabilities]
      properties:
        externalTenantId: { type: string }
        externalUserId: { type: string }
        capabilities: { type: array, items: { type: string } }
    MessageTimeline:
      type: object
      required: [messageId, providerMessageId, deliveryState, events]
      properties:
        messageId: { type: string, pattern: '^msg_' }
        providerMessageId: { type: [string, 'null'] }
        deliveryState: { type: [string, 'null'] }
        events:
          type: array
          items:
            type: object
            required: [kind, at, state]
            properties:
              kind: { type: string, enum: [command, delivery_job, provider_receipt, partner_webhook] }
              at: { type: string, format: date-time }
              state: { type: string }
              detail: { type: object, additionalProperties: true }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, correlationId]
          properties:
            code: { type: string }
            message: { type: string }
            correlationId: { type: string }
