openapi: 3.1.0
info:
  title: Border Connector Public API
  version: 2026-07-30
  description: |
    Versioned API for operating company-configured Border Connector agents.
    All operational endpoints require a scoped Bearer credential. Side-effecting operations require Idempotency-Key.
    This contract is available without authentication at /v1/openapi.yaml.
servers:
  - url: https://{host}/v1
    variables:
      host:
        default: api.example.com
security:
  - bearerAuth: []
paths:
  /credentials:
    get: { summary: List credentials by opaque cursor (admin scope), parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create credential and reveal its secret once (admin scope), parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /credentials/{id}/rotate:
    post: { summary: Rotate credential and reveal replacement secret once (admin scope), parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /contacts:
    get: { summary: List contacts by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create contact, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /contacts/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get: { summary: Get contact, responses: { '200': { $ref: '#/components/responses/Resource' } } }
    patch: { summary: Update contact, parameters: [ { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
    delete: { summary: Remove public contact profile, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '204': { description: Removed } } }
  /contacts/upsert:
    post: { summary: Upsert contact by external_id, parameters: [ { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '200': { $ref: '#/components/responses/Resource' }, '201': { $ref: '#/components/responses/Resource' } } }
  /contacts/{id}/export:
    post: { summary: Queue scoped contact export, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /contacts/{id}/erase:
    post: { summary: Queue auditable public-profile erasure, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /data-jobs:
    get: { summary: List contact export and erasure jobs by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /data-jobs/{id}:
    get: { summary: Get contact export or erasure job, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /conversations:
    get: { summary: List conversations by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create conversation, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /conversations/{id}:
    parameters: [ { $ref: '#/components/parameters/Id' } ]
    get: { summary: Get conversation, responses: { '200': { $ref: '#/components/responses/Resource' } } }
    patch: { summary: Update conversation, parameters: [ { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /conversations/{id}/messages:
    get: { summary: List normalized messages by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Add user message (modes: respond_only, dry_run, deliver); set stream=true for SSE agent-run events, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' }, { name: stream, in: query, schema: { type: boolean } } ], responses: { '200': { description: JSON result or text/event-stream with retry and event ids }, '202': { $ref: '#/components/responses/Resource' } } }
  /messages:
    post: { summary: Queue a direct SMS, email, or WhatsApp message, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /messages/{id}:
    get: { summary: Get normalized message, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /agent-runs:
    get: { summary: List agent runs by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /agent-runs/{id}:
    get: { summary: Get agent run, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /agent-runs/{id}/cancel:
    post: { summary: Cancel queued or running run, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /agent-runs/{id}/trace:
    get: { summary: Get sanitized run trace, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /ops/backlog:
    get: { summary: Inspect durable worker backlog counters (admin scope), responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /channels:
    get: { summary: List channel readiness and capabilities, responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /channels/{channel}:
    get: { summary: Get channel readiness, parameters: [ { name: channel, in: path, required: true, schema: { type: string } } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /channels/capabilities:
    get: { summary: Get account capabilities, responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /calls:
    get: { summary: List calls by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Queue an outbound call, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}:
    get: { summary: Get call, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}/events:
    get: { summary: List call lifecycle events, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /calls/{id}/hangup:
    post: { summary: Hang up an active provider-backed call when supported, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}/instructions:
    post: { summary: Inject instructions into an active STT-TTS phone session, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}/transfer:
    post: { summary: Transfer a live VoIP bridge call to a human agent, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}/transcript:
    get: { summary: Get conversation-backed call transcript when available, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /calls/{id}/recording:
    get: { summary: Get call recording metadata (status unavailable until capture exists), parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /media:
    post: { summary: Upload managed media (multipart field file), parameters: [ { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /media/{id}:
    get: { summary: Download managed media bytes or metadata with ?meta=true, parameters: [ { $ref: '#/components/parameters/Id' }, { name: meta, in: query, schema: { type: boolean } } ], responses: { '200': { description: Binary media or JSON metadata } } }
  /tasks:
    get: { summary: List scheduled tasks by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create scheduled task, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /tasks/{id}:
    get: { summary: Get task, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
    patch: { summary: Update task including schedule fields, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
    delete: { summary: Cancel task, parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '204': { description: Cancelled } } }
  /tasks/{id}/runs: { get: { summary: List task runs by opaque cursor, parameters: [ { $ref: '#/components/parameters/Id' }, { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } } }
  /tasks/preview:
    post: { summary: Dry-run preview for a Task payload (no external actions), responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /tasks/templates:
    get: { summary: List built-in Task templates, responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/templates/{id}:
    get: { summary: Get Task template, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /tasks/operational:
    get: { summary: List operational Tasks (checks, digests, exports, maintenance), responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/credentials:
    get: { summary: List Task credential metadata (no secrets), responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/exports:
    get: { summary: List company Task export artifacts (references only), responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/workflows:
    get: { summary: List workflows, responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/workflows/{id}/runs:
    get: { summary: List workflow runs, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/campaigns:
    get: { summary: List campaigns, responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /tasks/campaigns/{id}/recipients:
    get: { summary: List latest campaign recipients, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /webhook-endpoints:
    get: { summary: List webhook endpoints by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create endpoint and reveal signing secret once, responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /webhook-deliveries:
    get: { summary: List webhook deliveries by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /webhook-endpoints/{id}/test:
    post: { summary: Queue a signed webhook test delivery, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '202': { $ref: '#/components/responses/Resource' } } }
  /events:
    get: { summary: List durable API events by opaque cursor, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /usage:
    get: { summary: Aggregate model usage, responses: { '200': { $ref: '#/components/responses/Collection' } } }
  /usage/summary:
    get: { summary: Get aggregate usage summary, responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /calendar/availability:
    get: { summary: Get calendar busy ranges for the company provider (native/google/calcom/make), responses: { '200': { $ref: '#/components/responses/Resource' } } }
  /calendar/events:
    get: { summary: List calendar events for the company provider, parameters: [ { name: cursor, in: query, schema: { type: string } }, { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100 } } ], responses: { '200': { $ref: '#/components/responses/Collection' } } }
    post: { summary: Create calendar event or booking for the company provider, parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ], responses: { '201': { $ref: '#/components/responses/Resource' } } }
  /calendar/events/{id}:
    get: { summary: Get calendar event, parameters: [ { $ref: '#/components/parameters/Id' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
    patch: { summary: Update calendar event (native/google), parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '200': { $ref: '#/components/responses/Resource' } } }
    delete: { summary: Cancel calendar event (native/google), parameters: [ { $ref: '#/components/parameters/Id' }, { $ref: '#/components/parameters/OptionalIdempotencyKey' } ], responses: { '204': { description: Cancelled } } }
components:
  securitySchemes:
    bearerAuth: { type: http, scheme: bearer }
  parameters:
    Id: { name: id, in: path, required: true, schema: { type: string } }
    IdempotencyKey: { name: Idempotency-Key, in: header, required: true, schema: { type: string, minLength: 8, maxLength: 255 } }
    OptionalIdempotencyKey: { name: Idempotency-Key, in: header, required: false, schema: { type: string, minLength: 8, maxLength: 255 } }
  responses:
    Resource:
      description: Resource response
      content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } }
    Collection:
      description: Collection response
      content: { application/json: { schema: { $ref: '#/components/schemas/Envelope' } } }
  schemas:
    Envelope:
      type: object
      required: [data, meta]
      properties:
        data: {}
        meta: { type: object, properties: { request_id: { type: string }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] } } }
    Error:
      type: object
      properties: { error: { type: object, required: [type, code, message, request_id], properties: { type: { type: string }, code: { type: string }, message: { type: string }, request_id: { type: string } } } }
