> ## Documentation Index
> Fetch the complete documentation index at: https://flow9.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# A record's message timeline across SMS, email and WhatsApp

> Every outbound and inbound message tied to the record, newest first, whichever
channel or campaign it came from. Bodies are cut to 500 characters. The record
must be readable by the credential (404 otherwise). **Required scope:** `inbox:view`.




## OpenAPI

````yaml /api-reference/openapi.yaml get /v1/objects/{object}/records/{id}/messages
openapi: 3.1.0
info:
  title: Wander CRM Public API
  version: 1.0.0
  description: >
    The Wander CRM Public API enables third-party systems to integrate with

    Flow9 Engage CRM. Create leads, manage activities, retrieve customer

    profiles, and onboard new companies programmatically.


    ## Authentication

    Two credential types are accepted (see `securitySchemes`):

    - an **API key** in the `x-api-key` header — `f9_live_<32 hex>` for live
      traffic, `f9_test_<32 hex>` for the test sandbox (legacy `wcrm_live_` /
      `wcrm_test_` keys still authenticate but are no longer issued). Keys are
      created in **Settings > API Keys** or via `POST /v1/api-keys`.
    - an **OAuth 2.1 access token** in `Authorization: Bearer <jwt>` — what MCP
      clients and OAuth integrations send. Same scopes, same tenant isolation.

    If both are present, `x-api-key` wins. A request with neither is `401` with
    a

    `WWW-Authenticate: Bearer` header.


    These endpoints are **public** (no credential) — they are mounted before the

    auth middleware and apply their own per-IP rate limiting:

    - `GET /v1/health`

    - `POST /v1/onboarding` (tenant signup)

    - `GET /v1/availability` (module availability by country)


    `/v1/api-keys` is the one exception to the two credential types above: it is

    authenticated by a Company_Admin **session token** (Bearer), because a

    freshly-onboarded tenant has no API key yet.


    ## Rate limiting and quotas

    Three independent controls apply; a request must pass all of them.


    - **Per key** — sliding windows **per minute** (default 60, counted per
      endpoint) and **per hour** (default 1000, across the key). The
      `rate_limit_per_day` value stored on a key is **not enforced** in v1; it is
      kept for forward compatibility only.
    - **Per tenant** — one ceiling across every key the company holds (default
      600/minute, plus an optional daily cap derived from the plan). This is a
      protective backstop, not a meter.
    - **Monthly quota** — calls counted against the plan's `api_call_cap` for
    the
      billing period. Exhausted: `429 QUOTA_EXCEEDED`; no subscription: `402`;
      quota engine unreachable: `503 QUOTA_UNAVAILABLE` (fails closed).

    Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and

    `X-RateLimit-Reset`, describing whichever of the per-key / per-tenant
    windows

    is closest to rejecting. A `429` adds `Retry-After`. A monthly-quota refusal

    adds `X-Quota-Limit`, `X-Quota-Used` and `X-Quota-Remaining`. Full detail:

    `docs/api/rate-limits.md`.


    ## Response Envelope

    All responses use a consistent JSON envelope:

    ```json

    {
      "success": true | false,
      "data": { ... },          // present on success
      "error": { "code": "...", "message": "...", "details": ... }, // present on error
      "meta": { "request_id": "uuid", "timestamp": "ISO 8601" }
    }

    ```
  contact:
    name: Flow9 API Support
    email: api-support@flow9.online
  license:
    name: Proprietary
    url: https://flow9.online
servers:
  - url: https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api
    description: Production API
security:
  - ApiKeyAuth: []
  - BearerAuth: []
tags:
  - name: Health
    description: Service health check
  - name: Onboarding
    description: Company and admin user onboarding
  - name: Packages
    description: The sellable package catalogue (plans and add-on modules)
  - name: API Keys
    description: >-
      Headless API-key bootstrap — mint, list and revoke keys with a session
      token
  - name: Billing
    description: >-
      Read the tenant's plan, seats, trial, entitled modules, API quota and
      prepaid balance
  - name: Users
    description: >-
      The people in the workspace — list for assignment, create, deactivate,
      change role
  - name: Access control
    description: >-
      Read-only view of the authz engine: roles, permission profiles, the module
      × action matrix and role data-scope rules
  - name: Org structure
    description: >-
      The org tree (regions, countries, branches, teams), branches, and which
      users sit on which node
  - name: Settings
    description: >-
      Pipeline configuration: lead statuses and deal stages, tags, sources,
      duplicate-check fields, SLA policies
  - name: Scheduling
    description: >-
      When people are free and booking them: availability, slots, offer tokens,
      holds, assignments, bookings
  - name: Search & reports
    description: Cross-object search, aggregation, pipeline breakdown and saved reports
  - name: Attachments
    description: 'Files on records: upload, list, short-lived download links, delete'
  - name: Campaigns
    description: >-
      Read campaigns, their channels, runs and delivery stats; a record's
      message timeline
  - name: Messages
    description: 'Draft-and-approve messaging: prepare a message for a person to send'
  - name: Suppression
    description: Sending numbers, the do-not-call list and the email suppression list
  - name: Customers
    description: Customer profile retrieval
  - name: Leads
    description: Lead creation and management
  - name: Activities
    description: Deal and lead activity tracking
  - name: Objects
    description: >-
      Discover the objects and fields a key can see, and manage tenant-defined
      fields
  - name: Records
    description: >-
      Generic record CRUD, batch writes and per-record notes, tasks and
      activities
  - name: Webhooks
    description: Webhook subscriptions, deliveries, replays and the event catalogue
  - name: Test mode
    description: Sandbox controls available to `f9_test_` keys only
paths:
  /v1/objects/{object}/records/{id}/messages:
    get:
      tags:
        - Campaigns
      summary: A record's message timeline across SMS, email and WhatsApp
      description: >
        Every outbound and inbound message tied to the record, newest first,
        whichever

        channel or campaign it came from. Bodies are cut to 500 characters. The
        record

        must be readable by the credential (404 otherwise). **Required scope:**
        `inbox:view`.
      operationId: listRecordMessages
      parameters:
        - name: object
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Messages retrieved
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    type: object
                    properties:
                      messages:
                        type: array
                        items:
                          $ref: '#/components/schemas/Message'
                      next_cursor:
                        type:
                          - string
                          - 'null'
                      has_more:
                        type: boolean
                  meta:
                    $ref: '#/components/schemas/Meta'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  schemas:
    Message:
      type: object
      description: >-
        One SMS, email or WhatsApp message on a record, whichever table it lives
        in.
      required:
        - id
        - channel
        - direction
        - source
        - occurred_at
      properties:
        id:
          type: string
        channel:
          type: string
          enum:
            - sms
            - email
            - whatsapp
        direction:
          type: string
          enum:
            - outbound
            - inbound
        source:
          type: string
          description: Which table it came from (for support, not for logic).
        campaign_id:
          type:
            - string
            - 'null'
          format: uuid
        to:
          type:
            - string
            - 'null'
        from:
          type:
            - string
            - 'null'
        subject:
          type:
            - string
            - 'null'
        preview:
          type:
            - string
            - 'null'
          description: The body cut to 500 characters.
        status:
          type:
            - string
            - 'null'
        occurred_at:
          type: string
          format: date-time
    Meta:
      type: object
      properties:
        request_id:
          type: string
          format: uuid
        timestamp:
          type: string
          format: date-time
        rate_limit:
          type: object
          properties:
            limit:
              type: integer
            remaining:
              type: integer
            reset:
              type: integer
    ErrorBody:
      type: object
      required:
        - success
        - error
        - meta
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: >
                Human-readable message. Never contains internal detail, stack
                traces

                or upstream provider text.
            details:
              description: Additional context (field errors, available values, etc.)
        meta:
          $ref: '#/components/schemas/Meta'
    ErrorCode:
      type: string
      description: >
        Machine-readable error code. Branch on this, not on `message` — messages
        are

        human-readable and may change without notice.
      enum:
        - MISSING_API_KEY
        - AUTH_INVALID_KEY
        - API_KEY_EXPIRED
        - API_KEY_REVOKED
        - FORBIDDEN_SCOPE
        - IP_NOT_ALLOWED
        - MODULE_DISABLED
        - RATE_LIMIT_EXCEEDED
        - QUOTA_EXCEEDED
        - QUOTA_UNAVAILABLE
        - VALIDATION_ERROR
        - MISSING_REQUIRED_FIELD
        - INVALID_FIELD_FORMAT
        - NOT_FOUND
        - ALREADY_EXISTS
        - CONFLICT
        - SEAT_LIMIT_REACHED
        - NO_BILLING_CUSTOMER
        - AGREEMENT_ACTIVE
        - CARD_CHARGE_FAILED
        - NO_ACTIVE_SUBSCRIPTION
        - IDEMPOTENCY_KEY_REQUIRED
        - IDEMPOTENCY_CONFLICT
        - IDEMPOTENCY_IN_PROGRESS
        - INTERNAL_ERROR
        - SERVICE_UNAVAILABLE
        - METHOD_NOT_ALLOWED
  responses:
    BadRequest:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorBody'
          example:
            success: false
            error:
              code: VALIDATION_ERROR
              message: Request validation failed
              details:
                fields:
                  first_name: First name is required
            meta:
              request_id: 550e8400-e29b-41d4-a716-446655440000
              timestamp: '2026-02-15T10:00:00.000Z'
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorBody'
          example:
            success: false
            error:
              code: AUTH_INVALID_KEY
              message: Invalid API key
            meta:
              request_id: 550e8400-e29b-41d4-a716-446655440000
              timestamp: '2026-02-15T10:00:00.000Z'
    Forbidden:
      description: Insufficient scope, expired/revoked key, or IP not allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorBody'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorBody'
    TooManyRequests:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
        X-RateLimit-Limit:
          schema:
            type: integer
        X-RateLimit-Remaining:
          schema:
            type: integer
        X-RateLimit-Reset:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorBody'
          example:
            success: false
            error:
              code: RATE_LIMIT_EXCEEDED
              message: Rate limit exceeded. Retry after 12 seconds.
              details:
                limit: 60
                remaining: 0
                reset: 1718400000000
                retry_after: 12
            meta:
              request_id: 550e8400-e29b-41d4-a716-446655440000
              timestamp: '2026-02-15T10:00:00.000Z'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        API key from Settings > API Keys or `POST /v1/api-keys`. Format

        `f9_live_<32 hex chars>` (live) or `f9_test_<32 hex chars>` (sandbox —
        no

        real sends, ever). Legacy `wcrm_live_` / `wcrm_test_` keys still

        authenticate but are no longer issued. Shown once at creation; only a
        hash

        is stored.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        OAuth 2.1 access token (`Authorization: Bearer <jwt>`), issued by the

        Flow9 authorization server to MCP clients and OAuth integrations.
        Carries

        the same `module:action` scopes as an API key and is subject to the same

        rate limits and tenant isolation. On `/v1/api-keys` ONLY, this header

        carries a Company_Admin **session token** instead.

````