> ## 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.

# Create up to 100 records in one request

> Partial success is a NORMAL outcome. The response is 200 with `succeeded`
and `failed`; `failed` is always present (empty when everything worked)
and each entry carries the `index` of the item in the submitted array.
Always inspect `failed` - the HTTP status alone does not tell you whether
every record was written.

Whole-request problems are different and write nothing: more than 100
items, a malformed body, or a key without the create scope all return a
4xx with no side effects.

Items are validated and written individually through the same path as the
single-record endpoint, so a batch can never write something the single
endpoint would reject. If the workspace has duplicate checking configured,
items that duplicate an earlier item in the SAME batch are reported as
failed rather than written.

An `Idempotency-Key` header is required.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/objects/{object}/records:batch
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:batch:
    post:
      tags:
        - Records
      summary: Create up to 100 records in one request
      description: >
        Partial success is a NORMAL outcome. The response is 200 with
        `succeeded`

        and `failed`; `failed` is always present (empty when everything worked)

        and each entry carries the `index` of the item in the submitted array.

        Always inspect `failed` - the HTTP status alone does not tell you
        whether

        every record was written.


        Whole-request problems are different and write nothing: more than 100

        items, a malformed body, or a key without the create scope all return a

        4xx with no side effects.


        Items are validated and written individually through the same path as
        the

        single-record endpoint, so a batch can never write something the single

        endpoint would reject. If the workspace has duplicate checking
        configured,

        items that duplicate an earlier item in the SAME batch are reported as

        failed rather than written.


        An `Idempotency-Key` header is required.
      operationId: batchCreateRecords
      parameters:
        - name: object
          in: path
          required: true
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: array
                  items:
                    type: object
                    additionalProperties: true
                - type: object
                  properties:
                    records:
                      type: array
                      items:
                        type: object
                        additionalProperties: true
      responses:
        '200':
          description: Batch processed. Inspect `failed`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    $ref: '#/components/schemas/BatchResult'
                  meta:
                    $ref: '#/components/schemas/Meta'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          description: Batch rejected outright. No records were created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorBody'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  schemas:
    BatchResult:
      type: object
      required:
        - object
        - succeeded
        - failed
        - succeeded_count
        - failed_count
      properties:
        object:
          type: string
        succeeded:
          type: array
          items:
            type: object
            required:
              - index
              - record
            properties:
              index:
                type: integer
                description: Position of this item in the submitted array.
              record:
                type: object
                additionalProperties: true
        failed:
          type: array
          description: Always present. Empty when every item was written.
          items:
            type: object
            required:
              - index
              - code
              - message
            properties:
              index:
                type: integer
              code:
                type: string
              message:
                type: string
        succeeded_count:
          type: integer
        failed_count:
          type: integer
    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:
    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.

````