AIVIAIVIAPI Reference

Getting Started

  • Introduction
  • Authentication
  • OAuth Flow
  • Rate Limits
  • Errors
  • MCP Server

Organizations

  • Overview
  • GETList Organizations
  • GETGet Current Organization
  • GETGet Organization
  • POSTCreate Organization
  • PATCHUpdate Organization

Phone Numbers

  • Overview
  • GETList Phone Numbers
  • GETGet Phone Number
  • GETSearch Available Numbers
  • POSTBuy a Number
  • PATCHUpdate a Number
  • DELETERelease a Number

AI Agents

  • Overview
  • GETList AI Agents
  • GETGet AI Agent
  • POSTCreate AI Agent
  • PATCHUpdate AI Agent
  • POSTDuplicate AI Agent
  • DELETEDelete AI Agent

Experts

  • Overview
  • GETList Experts
  • GETGet Expert
  • POSTInvoke Expert

Contacts

  • Overview
  • GETList Contacts
  • GETGet Contact
  • POSTCreate Contact
  • PUTUpdate Contact
  • DELETEDelete Contact
  • GETList DNC Records
  • POSTMark Contact DNC
  • DELETERevoke DNC
  • POSTSuppress a Channel
  • DELETEUn-suppress a Channel
  • POSTSuppress an Email
  • DELETEUn-suppress an Email

Insights

  • Overview
  • POSTQuery Insights
  • POSTEnrich Contact

Calls

  • Overview
  • GETList Calls
  • GETGet Call
  • POSTInitiate Call

Call Intelligence

  • Overview
  • GETGet Call Intelligence

Recordings

  • Overview
  • GETGet Recording

Messages

  • Overview
  • GETList Messages
  • GETGet Message
  • POSTSend Message

Emails

  • Overview
  • GETList Emails
  • GETGet Email
  • POSTSend Email

Workflows

  • Overview
  • GETList Workflows
  • GETGet Workflow
  • POSTCreate Workflow
  • PUTUpdate Workflow
  • DELETEDelete Workflow
  • POSTActivate Workflow
  • POSTDeactivate Workflow
  • POSTDuplicate Workflow
  • POSTEnroll Contact
  • Enrollments
  • GETList Enrollments
  • DELETECancel Enrollment

Rubrics

  • Overview
  • GETList Rubrics
  • GETGet Rubric
  • POSTCreate Rubric

Scorecards

  • Overview
  • GETList Scorecards
  • GETGet Scorecard

Chat Widgets

  • Overview
  • GETList Chat Widgets
  • GETGet Chat Widget
  • POSTCreate Chat Widget
  • PUTUpdate Chat Widget
  • DELETEDelete Chat Widget

Landing Pages

  • Overview
  • GETList Landing Pages
  • GETGet Landing Page
  • POSTCreate Landing Page
  • PATCHUpdate Landing Page
  • DELETEDelete Landing Page

AIVI REST API

The AIVI REST API allows you to programmatically manage your CRM data. Use it to create integrations, sync contacts with external systems, or build custom workflows.

All API requests are made to the following base URL. Requests and responses use JSON format.

https://app.aivi.io/ignite/api/v1

Machine-readable spec: GET /openapi.json (OpenAPI 3.1) — use it to generate a client.

Content-Type: application/json for all request and response bodies.

Authentication

Authenticate API requests using a Bearer token in the Authorization header. Two token types are supported:

  • API keys (aivi_sk_…) — for server-to-server integrations. Created in Settings > Developer > API Keys. Each key carries an explicit list of scopes.
  • OAuth bearer tokens — for user-driven clients (the AIVI MCP server, third-party agents, your own integrations). Obtain via the OAuth flow at https://app.aivi.io/oauth/authorize (see below). Scopes are derived from the user's organization role: owner / admin get all scopes, member gets read-mostly + send messaging, agent gets read + send messaging only. Multi-org users must include the X-Aivi-Organization header to select the active organization.
bash
# API key (server-to-server)
curl https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer aivi_sk_your_api_key_here"

# OAuth bearer (user-driven)
curl https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "X-Aivi-Organization: <organization_id>"

Available Scopes

Each API key is assigned specific scopes that control what operations it can perform.

NameTypeRequiredDescription
contacts:readscope
Optional
List and view contacts
contacts:writescope
Optional
Create and update contacts
contacts:deletescope
Optional
Delete contacts
calls:readscope
Optional
List and view calls, including transcripts and recording URLs
messages:readscope
Optional
List and view SMS/MMS/WhatsApp messages
messages:writescope
Optional
Send SMS messages to contacts
emails:readscope
Optional
List and view sent and received emails
emails:writescope
Optional
Send emails to contacts
workflows:readscope
Optional
List workflows, read workflow detail (including the full graph), and read enrollments
workflows:enrollscope
Optional
Enroll contacts in workflows and cancel enrollments
workflows:writescope
Optional
Create, update, duplicate, activate, and deactivate workflows
workflows:deletescope
Optional
Delete workflows (cascades to nodes, edges, and enrollments)
rubrics:readscope
Optional
List and view QA rubrics and their criteria
rubrics:writescope
Optional
Create new QA rubrics
scorecards:readscope
Optional
List and view QA evaluation scorecards
calls:initiatescope
Optional
Dispatch outbound AI agent calls (POST /calls). Reserves call funds.
phone_numbers:readscope
Optional
List and view the org's purchased phone numbers
phone_numbers:writescope
Optional
Update phone numbers and assign AI agents
phone_numbers:purchasescope
Optional
Buy and release phone numbers (spends funds)
ai_agents:readscope
Optional
List and view the org's AI voice agents
ai_agents:writescope
Optional
Create, update and duplicate AI agents
ai_agents:deletescope
Optional
Archive or permanently delete AI agents
organizations:readscope
Optional
View the current organization
organizations:writescope
Optional
Create and update client sub-organizations. Agency keys only — an organization key cannot carry it
contacts:dnc_managescope
Optional
Revoke Do Not Contact records
insights:enrichscope
Optional
Run paid AIVI Insights enrichment
experts:readscope
Optional
List and view Foundry Experts
experts:invokescope
Optional
Run Foundry Experts
chat_widgets:readscope
Optional
List and view chat widgets
chat_widgets:writescope
Optional
Create, update and delete chat widgets
landing_pages:readscope
Optional
List and view landing pages
landing_pages:writescope
Optional
Create, update and delete landing pages

Agency Keys

An agency key is an API key that belongs to an agency hub organization and can act on the hub and on every active organization of the same agency. Hub admins create one in Settings > Developer > API Keys by choosing key type Agency. Only an admin or owner of the hub can create one, and only while the hub is an active agency organization. The scope organizations:write (create and update sub-organizations) can only be granted to agency keys.

To act on a client organization, send its id in the X-Aivi-Organizationheader. Without the header, or with the hub's own id, the call acts on the hub. Any organization the key cannot reach — one of another agency, an inactive one, one that does not exist, a malformed id, or any organization once the hub no longer qualifies as an agency hub — gets the same response, so the API never reveals which organizations exist:

json
{
  "success": false,
  "error": "Organization not accessible with this API key",
  "code": "organization_not_accessible",
  "timestamp": "2026-10-03T12:00:00.000Z"
}

Behaviour change for organization keys: a regular organization key that sends X-Aivi-Organization naming a different organization is now rejected with 403 organization_not_accessible on every endpoint except GET /organizations, which ignores the header. Previously the header was ignored everywhere. If your client sends a stale or hard-coded header, remove it (the header naming the key's own organization is still fine).

Agency keys share one combined limit of 600 requests per minute across all the organizations they act on, in addition to the per-organization limit (see Rate Limits).

bash
curl https://app.aivi.io/ignite/api/v1/ai_agents \
  -H "Authorization: Bearer aivi_sk_your_agency_key_here" \
  -H "X-Aivi-Organization: <client_organization_id>"

Multi-Organization Users

Organization API keys are bound to a single organization at creation time and don't need any additional headers. Agency keys use the header to pick a client organization (see above).

OAuth users who belong to multiple organizations must specify which org they're acting as via the X-Aivi-Organization header. To discover the org IDs you're a member of, call GET /organizations — it works without the header so you can fetch the list before choosing one.

Single-org users can omit the header and the active org is auto-resolved.

bash
# Step 1 — discover your orgs (no X-Aivi-Organization needed)
curl https://app.aivi.io/ignite/api/v1/organizations \
  -H "Authorization: Bearer <oauth_access_token>"

# Step 2 — call any other endpoint, scoped to one of those orgs
curl https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "X-Aivi-Organization: 440e8400-e29b-41d4-a716-446655440000"

OAuth Flow

For user-driven integrations, AIVI provides an OAuth 2.0 authorization-code flow with a hosted sign-in page. Send your user to https://app.aivi.io/oauth/authorize; they sign in to AIVI (email + password, Google, or Microsoft) and then explicitly authorize your app on a consent screen; you receive an access token + refresh token bound to their account. You don't need to know or care which auth method they use — adding a new one won't change your integration code.

redirect_to URLs must use HTTPS (or be localhost for local development). For production, contact AIVI support to add yours to your workspace allowlist.

1. Send the user to the AIVI sign-in page

Browser-side. Redirect the user to:

bash
https://app.aivi.io/oauth/authorize
  ?redirect_to=https://your-app.com/callback
  &state=<random_state>

state is an opaque string echoed back unchanged so you can correlate the redirect with the user session that started the flow (and protect against CSRF). Generate a fresh random value per attempt.

The user signs in (or is auto-detected if already signed in to AIVI) and clicks "Allow Access" on the consent screen. They're then redirected to your redirect_to with ?code=…&state=…. If they click "Deny" the redirect carries ?error=access_denied.

2. Exchange the code for tokens

Server-side. Codes are single-use and expire after 10 minutes.

bash
curl -X POST "https://app.aivi.io/ignite/api/v1/auth/token?grant_type=authorization_code" \
  -H "Content-Type: application/json" \
  -d '{ "code": "<code from redirect>" }'

Response:

json
{
  "access_token": "eyJhbGciOi…",
  "token_type": "bearer",
  "expires_in": 3600,
  "expires_at": 1735689600,
  "refresh_token": "v2.A1B2C3…",
  "user": {
    "id": "<user_id>",
    "email": "<user_email>"
  }
}

3. Use the access token

bash
curl https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer <access_token>" \
  -H "X-Aivi-Organization: <organization_id>"

4. Refresh when the access token expires

Access tokens expire after expires_in seconds (typically 3600). Call /auth/token with grant_type=refresh_token.

bash
curl -X POST "https://app.aivi.io/ignite/api/v1/auth/token?grant_type=refresh_token" \
  -H "Content-Type: application/json" \
  -d '{ "refresh_token": "<refresh_token>" }'

Returns the same shape as step 2, with a new access_token and refresh_token. The previous refresh_token is invalidated.

When to use OAuth vs. API keys

  • OAuth: your app acts on behalf of a user (multi-tenant SaaS, AI agents, browser extensions).
  • API keys: server-to-server with a single, stable identity.

Rate Limits

The API is rate limited to 100 requests per minute per API key (or OAuth user) per organization. If you exceed this limit, you will receive a 429 response. Agency keys are additionally limited to 600 requests per minute combined across every organization they act on (including GET /organizations).

Two endpoints that are called before an organization is known are limited per client IP address instead: POST /auth/token at 60 requests per minute and GET /organizations at 120 requests per minute.

We recommend implementing exponential backoff in your integration to gracefully handle rate limit errors.

Idempotency-Key

Endpoints that accept an Idempotency-Key header (1–255 characters) can be retried safely: the first response for a key is remembered for 24 hours.

  • A retry with the same key and the same request replays the stored response with the header Idempotent-Replayed: true; the operation does not run again.
  • Only successful (2xx) responses and 400, 404 and 422 errors are stored. Any other response (for example 403, 409, 429 or 5xx) is not, so a retry with the same key runs the request again.
  • 409 idempotency_key_in_progress: a request with this key is still running — retry later.
  • 409 idempotency_key_outcome_unknown: an earlier request with this key did not finish and its outcome is unknown — check the resource state, then retry with a new key.
  • 422 idempotency_key_reused: the key was already used with a different request body (or query string).

Rate Limit Response
429

json
{
  "success": false,
  "error": "Rate limit exceeded. Please slow down.",
  "code": "rate_limited",
  "timestamp": "2026-02-26T12:00:00.000Z"
}

Errors

The API returns consistent error responses across all endpoints. Errors include a human-readable message and a machine-readable error code.

Error Response Format

json
{
  "success": false,
  "error": "Human-readable error message",
  "code": "machine_readable_code",
  "timestamp": "2026-02-26T12:00:00.000Z"
}

Error Codes

NameTypeRequiredDescription
400validation_error
Optional
Invalid request parameters or body. On contacts this also covers sending phones together with phone/secondary_phone ("Provide either 'phones' or 'phone'/'secondary_phone', not both."), emails together with email ("Provide either 'emails' or 'email', not both."), and creating a contact with no identifier at all — no phone, no email, and no non-empty phones/emails list
400invalid_body
Optional
Request body is not valid JSON
400invalid_phones
Optional
The phones list could not be saved
400invalid_emails
Optional
The emails list could not be saved
400reserved_tag
Optional
The system-managed DNC tag cannot be added or removed via PUT /contacts/:id — use POST/DELETE /contacts/:id/dnc
401unauthorized
Optional
Missing or invalid API key / OAuth token
403insufficient_scope
Optional
Caller lacks the required scope for this operation
400org_required
Optional
OAuth user belongs to multiple orgs; set the X-Aivi-Organization header. Response includes available_organization_ids.
403org_membership_required
Optional
OAuth user is not a member of the requested X-Aivi-Organization
403organization_not_accessible
Optional
The X-Aivi-Organization header names an organization this key cannot act on (an organization key naming a different organization, or an agency key naming an organization outside its agency, inactive, or unknown). The same response for all of these
404not_found
Optional
The requested resource does not exist
409duplicate_phone
Optional
A contact with this phone number already exists. Returned as 400 instead when the collision is raised while writing a phones list
409duplicate_email
Optional
A contact with this email already exists. Returned as 400 instead when the collision is raised while writing an emails list
402insufficient_funds
Optional
Insufficient account funds for a paid operation (e.g. AIVI Insights enrichment)
422no_enrichment_data
Optional
Contact lacks sufficient data for enrichment (needs name, phone, email, or address)
429rate_limited
Optional
Rate limit exceeded (100 req/min)
500server_error
Optional
An unexpected server error occurred

MCP Server

The AIVI MCP server exposes the same scopes and endpoints documented on this page as conversational tools for AI clients (Claude Desktop, ChatGPT Developer Mode, Claude Code, OpenClaw, Smithery, and any client that supports the Model Context Protocol).

Connect your AIVI account once via OAuth, then use natural language — “list my contacts named Smith,” “send 'confirming our 3pm' to contact 4f3a…,” “enroll +12065551234 in a 3-day sequence.”

Endpoint: https://mcp.aivi.io/mcp
Transport: HTTP (streamable-http)
Auth: OAuth 2.1 + PKCE (automatic) orAIVI_API_KEY env var

Connect via Claude Code

bash
claude mcp add --transport http \
  --callback-port 54513 \
  aivi https://mcp.aivi.io/mcp

Then in any session: /mcp → select aivi → Authenticate.

Other clients

  • Claude Desktop / claude.ai: Settings → Connectors → Add → URL https://mcp.aivi.io/mcp
  • ChatGPT Developer Mode: Settings → Connectors → Developer Mode → Add URL
  • OpenClaw / NemoClaw: clawhub install aivi-engagement
  • Any MCP client: point at https://mcp.aivi.io/mcp — supports OAuth 2.1 + PKCE.

Organizations

Every API call is scoped to a single organization (resolved from your API key, or from the X-Aivi-Organization header on OAuth and agency keys). The Organizations endpoints let you introspect membership and the active org, and — with an agency key — create and manage the client organizations of your agency.

GET /organizations lists every org the caller belongs to (or the single org an organization key is bound to; for an agency key, the hub plus every active organization of the agency; if the hub no longer qualifies as an agency hub, only the hub is listed) — call this first if you're an OAuth user in multiple organizations and don't yet know which one to act as. It deliberately works without the X-Aivi-Organization header so you can discover org IDs before picking one.

GET /organizations/me returns the currently-active org once you've picked one (or for single-org/API-key callers). GET /organizations/:id returns one organization with its billing and provisioning state.

Creating and managing client organizations

POST /organizations creates a client (sub-)organization of your agency and PATCH /organizations/:id updates it. Both need the organizations:write scope, which only agency keys can carry. For OAuth, in v1 only platform superusers hold this scope (and they must also administer the active agency hub); other users get 403 insufficient_scope. Organizations are never deleted through the API.

Create or clone. A new organization starts with the platform defaults. Send clone_from (the id of another active organization of your agency) to copy its configuration instead. Settings you send are merged over the source's (or the defaults); settings you omit keep that value. On PATCH the settings object is merged the same way: keys you send replace the current value, keys you omit stay unchanged.

Clone limitations. A clone copies configuration only. Phone numbers, billing and wallet state, and every voice-provider resource are not copied. The clone does copy the source's voice provider, logo and auto-insights setting. Cloned workflows start inactive — review and activate them. Cloned AI agents are provisioned with the voice provider automatically (their sync_status starts as pending), and they get no phone numbers. Landing pages are copied pausedwith a new, random subdomain — review, rename and publish them. Custom organization roles (with their permissions), saved contact views (owned by the key's creator; skipped when the creator is no longer an agency admin), greeting templates and approval-suggestion settings are copied too. Not copied: knowledge-base documents, Foundry agents and their Skills, approved agent instructions, feature-flag overrides and role assignments.

Billing. billing.funds decides who pays for the organization's usage: agency spends the agency hub's pooled funds, own uses the organization's own wallet. billing.subscription_price_cents is the monthly resale price your agency charges the organization (in cents; it cannot be below the standard plan price: 422 price_below_floor; on PATCH send null to clear it). Setting a price on an organization that has none is charged immediately; changing an existing price keeps the current billing date. An organization with no card on file is flagged past_due on the next billing run. The hub itself has no billing settings (422 hub_billing_not_allowed).

Not a trial organization. Organizations created through the API are never trial organizations (is_trial_mode is false): they are billed through the agency or their own wallet, so they can buy phone numbers through the API.

Provisioning. Creation returns 202 straight away; telephony, voice and email are set up in the background. Poll GET /organizations/:id and read the provisioning object: status is ready when billing, telephony and voice are all ready, failed when any of them failed, otherwise pending. email (the platform email domain) is informational and never affects status; it is skipped when no domain is provisioned. A telephony or voice step that is not ready after 15 minutes, or that records a provisioning error, is failed; contact support if that happens. Billing is set up synchronously. Email fails after a failed status or 8 attempts (email doesn't affect the overall status).

Idempotency. POST /organizations requires an Idempotency-Key header, shared by all keys and users of your agency. Repeating the same request with the same key never creates a second organization: it returns the same organization with Idempotent-Replayed: true. A replay within 24 hours returns the stored first response — poll GET /organizations/:id for current provisioning state. Reusing a key with a different request is 422 idempotency_key_reused. If an earlier attempt with the key crashed or was interrupted, retry with the SAME key: it is safely retried and replays the same organization, never a second one. While an attempt is still running you may get 409 idempotency_key_in_progress (retry later). Deleting an organization frees its Idempotency-Key — retrying the same key afterwards creates a new organization.

GET

List Organizations

https://app.aivi.io/ignite/api/v1/organizations
(no scope required)

Returns every organization the caller belongs to. Each item includes the caller's role in that org. Unlike every other endpoint, this one does NOT require X-Aivi-Organization — it's how multi-org OAuth users discover their org IDs in the first place. Organization keys see the single org the key is bound to (with role='api_key'). Agency keys (scope organizations:read required) see the hub plus every active organization of the agency (only the hub if it no longer qualifies as an agency hub), with role='api_key' and the extra fields is_hub and is_active; they share a combined 600 requests/minute limit.

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/organizations \
  -H "Authorization: Bearer <oauth_access_token>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "440e8400-e29b-41d4-a716-446655440000",
      "name": "Acme Inc.",
      "slug": "acme",
      "logo": null,
      "description": null,
      "settings": {
        "currency": "USD",
        "language": "en",
        "timezone": "America/Los_Angeles",
        "dateFormat": "MM/DD/YYYY"
      },
      "business_industry": "software",
      "business_website": "https://acme.example",
      "created_at": "2026-01-10T10:00:00Z",
      "updated_at": "2026-04-22T16:14:00Z",
      "role": "admin"
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "name": "Bomb CRM",
      "slug": "bomb-crm",
      "logo": null,
      "description": null,
      "settings": {
        "currency": "USD",
        "language": "en",
        "timezone": "UTC",
        "dateFormat": "MM/DD/YYYY"
      },
      "business_industry": null,
      "business_website": null,
      "created_at": "2026-02-14T08:30:00Z",
      "updated_at": "2026-04-30T09:00:00Z",
      "role": "member"
    }
  ]
}
GET

Get Current Organization

https://app.aivi.io/ignite/api/v1/organizations/me
organizations:read

Returns the organization the caller is scoped to. Stripe IDs, internal vendor IDs, and KYC PII are not returned.

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/organizations/me \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "440e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Inc.",
    "slug": "acme",
    "logo": null,
    "description": null,
    "settings": {
      "currency": "USD",
      "language": "en",
      "timezone": "America/Los_Angeles",
      "dateFormat": "MM/DD/YYYY"
    },
    "business_industry": "software",
    "business_website": "https://acme.example",
    "created_at": "2026-01-10T10:00:00Z",
    "updated_at": "2026-04-22T16:14:00Z"
  }
}
GET

Get Organization

https://app.aivi.io/ignite/api/v1/organizations/:id
organizations:read

Returns one organization with its hub flag, billing and provisioning state. An organization key can read its own organization; an agency key can read the hub and any active organization of its agency; an OAuth user can read organizations they belong to. Anything else is a 404 not_found (the API does not reveal which organizations exist). Poll this endpoint after POST /organizations until provisioning.status is ready.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The organization id

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/organizations/660e8400-e29b-41d4-a716-446655440002 \
  -H "Authorization: Bearer aivi_sk_your_agency_key_here"

Response
200

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440002",
    "name": "Acme Dental",
    "slug": "acme-dental",
    "logo": null,
    "description": "Dental clinic, Austin",
    "settings": {
      "currency": "USD",
      "language": "en",
      "timezone": "America/Chicago",
      "dateFormat": "MM/DD/YYYY"
    },
    "business_industry": null,
    "business_website": null,
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z",
    "is_hub": false,
    "is_active": true,
    "billing": {
      "funds": "agency",
      "subscription_price_cents": 29900
    },
    "provisioning": {
      "billing": "ready",
      "status": "pending",
      "telephony": "pending",
      "voice": "pending",
      "email": "pending"
    }
  }
}
POST

Create Organization

https://app.aivi.io/ignite/api/v1/organizations
organizations:write

Create a client sub-organization of your agency (agency keys only). Requires an Idempotency-Key header (1-255 characters): a retry with the same key and request returns the same organization with Idempotent-Replayed: true instead of creating another. Deleting an organization frees its Idempotency-Key: retrying the same key afterwards creates a new organization. Returns 202 once the organization exists; telephony, voice and email are provisioned in the background, so poll GET /organizations/:id and watch provisioning. The Try it panel can't send the Idempotency-Key header, so use curl. A cloned organization copies configuration only (see the Organizations overview). Errors: 400 validation_error / invalid_slug / idempotency_key_required, 403 insufficient_scope (not an agency key, or for OAuth not an admin of an agency hub), 403 hub_not_agency (the key's organization is no longer an active agency hub), 403 organization_not_accessible (clone_from is not an active organization of your agency), 409 slug_taken, 409 organization_limit_reached, 409 idempotency_key_in_progress (retry later; a crashed attempt is retried safely with the same key), 422 price_below_floor, 422 idempotency_key_reused. 400, 404 and 422 responses are stored under the key for 24 hours: after fixing the cause, retry with a NEW Idempotency-Key.

Request Body

NameTypeRequiredDescription
namestring
Required
Organization name (1-120 characters)
slugstring
Optional
Lowercase letters, digits and single hyphens, at most 63 characters (e.g. acme-dental). Generated from the name when omitted. Cannot be changed later; a taken slug is 409 slug_taken
descriptionstring
Optional
Up to 2000 characters
settingsobject
Optional
Any of timezone (IANA, e.g. America/Chicago), language (e.g. en, en-US), currency (ISO 4217, e.g. USD), dateFormat. Omitted keys take the platform default, or the clone source value when clone_from is set
clone_fromuuid
Optional
Another active organization of your agency whose configuration (agents, workflows, fields, landing pages (paused), custom roles, ...) is copied. No phone numbers, billing, provider state, knowledge documents or Foundry agents
billingobject
Optional
funds: "agency" (default; spends the hub's pooled funds) or "own" (its own wallet). subscription_price_cents: monthly resale price in cents (not below the standard plan price). Setting a price charges immediately; an organization with no card on file is flagged past_due on the next billing run

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/organizations \
  -H "Authorization: Bearer aivi_sk_your_agency_key_here" \
  -H "Idempotency-Key: <unique-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Dental",
    "slug": "acme-dental",
    "description": "Dental clinic, Austin",
    "settings": { "timezone": "America/Chicago" },
    "clone_from": "<source_organization_id>",
    "billing": { "funds": "agency", "subscription_price_cents": 29900 }
  }'

Response
202

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440002",
    "name": "Acme Dental",
    "slug": "acme-dental",
    "logo": null,
    "description": "Dental clinic, Austin",
    "settings": {
      "currency": "USD",
      "language": "en",
      "timezone": "America/Chicago",
      "dateFormat": "MM/DD/YYYY"
    },
    "business_industry": null,
    "business_website": null,
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z",
    "is_hub": false,
    "is_active": true,
    "billing": {
      "funds": "agency",
      "subscription_price_cents": 29900
    },
    "provisioning": {
      "billing": "ready",
      "status": "pending",
      "telephony": "pending",
      "voice": "pending",
      "email": "pending"
    }
  }
}
PATCH

Update Organization

https://app.aivi.io/ignite/api/v1/organizations/:id
organizations:write

Update a client organization of your agency, or the hub's own profile (agency keys only). Send only the fields to change; unknown fields (including slug, which is immutable) are a 400. settings is merged: keys you send replace the current value, keys you omit are kept. description can be set to an empty string but not to null. billing (funds, subscription_price_cents; send null to clear the price) applies to sub-organizations only: on the hub it is 422 hub_billing_not_allowed. A new resale price is charged immediately, and an organization with no card on file is flagged past_due on the next billing run. Errors: 400 validation_error, 403 insufficient_scope, 403 hub_not_agency, 404 not_found (not an organization of your agency), 422 hub_billing_not_allowed, 422 price_below_floor.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The organization id

Request Body

NameTypeRequiredDescription
namestring
Optional
Organization name (1-120 characters)
descriptionstring
Optional
Up to 2000 characters. An empty string clears it; null is not accepted
settingsobject
Optional
Any of timezone, language, currency, dateFormat; merged into the current settings
billingobject
Optional
funds: "agency" or "own"; subscription_price_cents: monthly resale price in cents, or null to clear it

Example Request

bash
curl -X PATCH https://app.aivi.io/ignite/api/v1/organizations/660e8400-e29b-41d4-a716-446655440002 \
  -H "Authorization: Bearer aivi_sk_your_agency_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "settings": { "timezone": "America/New_York" },
    "billing": { "subscription_price_cents": 34900 }
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440002",
    "name": "Acme Dental",
    "slug": "acme-dental",
    "logo": null,
    "description": "Dental clinic, Austin",
    "settings": {
      "currency": "USD",
      "language": "en",
      "timezone": "America/Chicago",
      "dateFormat": "MM/DD/YYYY"
    },
    "business_industry": null,
    "business_website": null,
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z",
    "is_hub": false,
    "is_active": true,
    "billing": {
      "funds": "agency",
      "subscription_price_cents": 29900
    },
    "provisioning": {
      "billing": "ready",
      "status": "ready",
      "telephony": "ready",
      "voice": "ready",
      "email": "ready"
    }
  }
}

Phone Numbers

Phone numbers are the Twilio numbers your organization owns. They're used for inbound and outbound voice + SMS. Through the API you can search for numbers to buy, buy one, update its name, agents and call forwarding, and release it.

Buying is restricted. Trial organizations can't buy numbers through the API unless your account has been exempted (contact support): 403 trial_purchase_not_allowed. 422 regulatory_bundle_required means the number's country needs an approved regulatory bundle (set it up in the dashboard), or a bundle is still under review. Search results show requires_bundle for such numbers. An organization whose telephony is not yet provisioned gets 409 organization_not_ready.

Pricing is reported in cents on every search result: setup_price_cents is charged once when you buy and monthly_price_cents is the monthly rental. Both are charged to the organization's funds; a value of 0 means the organization is exempt (for example, it brings its own Twilio account). Trial organizations also see a setup_price_cents of 0, but they can't buy through the API (403 trial_purchase_not_allowed). A purchase with insufficient funds returns 402 insufficient_funds.

POST /phone_numbers requires an Idempotency-Key header (1–255 characters). A retry with the same key and the same request replays the first response for 24 hours, marked Idempotent-Replayed: true, so a network failure never buys a second number. Generate a fresh UUID for every purchase. The Try it panel can't send the Idempotency-Key header, so use curl for purchases. A missing key is a 400 idempotency_key_required. 400, 404 and 422 responses are stored under the key for 24 hours too: after fixing the cause of a 400 or 422 (for example, getting a regulatory bundle approved), retry with a NEW key. 402, 403 and 409 responses are not stored, so retrying them with the same key is fine. The same key with a different request is a 422 idempotency_key_reused. While the first request is still running, a retry returns 409 idempotency_key_in_progress; if the first attempt crashed, a retry returns 409 idempotency_key_outcome_unknown, and you should check GET /phone_numbers before buying again. 502 purchase_incomplete means the number was bought but could not be completed or released: contact support, and do not retry (a retry with the same key replays the same 502).

DELETE /phone_numbers/:id releases a number AIVI bought for you back to the carrier. A number you imported from your own Twilio account is disconnected from AIVI instead and stays in your account; the response action is released or disconnected.

POST and PATCH responses carry a warningsarray when part of the request did not take (for example the number was bought but the AI agent could not be assigned, or could not be routed to the agent's voice provider). The number itself is saved; retry the update to apply the rest.

Each number has an ai_enabled flag indicating whether it's wired up for AI agent outbound dialing. Only AI-enabled numbers can be used as the from_phone_number_id when initiating calls via POST /calls.

The Phone Number Object

json
{
  "id": "880e8400-e29b-41d4-a716-446655440000",
  "organization_id": "440e8400-e29b-41d4-a716-446655440000",
  "phone_number": "+12027778888",
  "friendly_name": "Main Sales Line",
  "capabilities": {
    "voice": true,
    "sms": true,
    "mms": false,
    "fax": false
  },
  "is_default": true,
  "is_active": true,
  "ai_enabled": true,
  "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
  "outbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
  "forward_to": null,
  "assigned_to": null,
  "created_at": "2026-04-01T12:00:00Z",
  "updated_at": "2026-05-01T08:30:00Z"
}
GET

List Phone Numbers

https://app.aivi.io/ignite/api/v1/phone_numbers
phone_numbers:read

Returns the organization's purchased phone numbers, paginated.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number (default 1).
page_sizeinteger
Optional
Items per page, 1–100 (default 20).
is_activeboolean
Optional
Filter to active or inactive numbers.
ai_enabledboolean
Optional
Filter to numbers wired up for AI agent calling.
sortstring
Optional
created_at|phone_number, optional :asc / :desc.

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/phone_numbers?ai_enabled=true \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "880e8400-e29b-41d4-a716-446655440000",
      "organization_id": "440e8400-e29b-41d4-a716-446655440000",
      "phone_number": "+12027778888",
      "friendly_name": "Main Sales Line",
      "capabilities": {
        "voice": true,
        "sms": true,
        "mms": false,
        "fax": false
      },
      "is_default": true,
      "is_active": true,
      "ai_enabled": true,
      "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
      "outbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
      "forward_to": null,
      "assigned_to": null,
      "created_at": "2026-04-01T12:00:00Z",
      "updated_at": "2026-05-01T08:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Phone Number

https://app.aivi.io/ignite/api/v1/phone_numbers/:id
phone_numbers:read

Retrieve a single phone number by its unique ID.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The phone number ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/phone_numbers/880e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "phone_number": "+12027778888",
    "friendly_name": "Main Sales Line",
    "capabilities": {
      "voice": true,
      "sms": true,
      "mms": false,
      "fax": false
    },
    "is_default": true,
    "is_active": true,
    "ai_enabled": true,
    "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "outbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "forward_to": null,
    "assigned_to": null,
    "created_at": "2026-04-01T12:00:00Z",
    "updated_at": "2026-05-01T08:30:00Z"
  }
}
GET

Search Available Numbers

https://app.aivi.io/ignite/api/v1/phone_numbers/available
phone_numbers:read

Search for phone numbers you can buy. Each result includes requires_bundle (buying needs an approved regulatory bundle), monthly_price_cents and setup_price_cents (0 when the organization is exempt; also 0 for trial organizations, which can't buy through the API and get 403 trial_purchase_not_allowed). Results are not reserved: a number can be taken before you buy it, in which case POST /phone_numbers returns 409 number_unavailable.

Query Parameters

NameTypeRequiredDescription
countrystring
Optional
ISO 3166-1 alpha-2 country code, e.g. US (default US).
typestring
Optional
local|toll_free|mobile (default local).
area_codestring
Optional
Three-digit US/CA area code (local numbers only).
containsstring
Optional
Digits the number must contain, 1–10 characters; * matches any digit.
limitinteger
Optional
Maximum results, 1–50 (default 20).

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/phone_numbers/available?country=US&type=local&area_code=415&limit=5" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "phone_number": "+14155550123",
      "friendly_name": "(415) 555-0123",
      "locality": "San Francisco",
      "region": "CA",
      "postal_code": "94105",
      "iso_country": "US",
      "capabilities": {
        "voice": true,
        "sms": true,
        "mms": true,
        "fax": false
      },
      "requires_bundle": false,
      "monthly_price_cents": 115,
      "setup_price_cents": 100
    }
  ]
}
POST

Buy a Number

https://app.aivi.io/ignite/api/v1/phone_numbers
phone_numbers:purchase

Buy a phone number and add it to the organization. Requires an Idempotency-Key header (1–255 characters): a retry with the same key replays the first response for 24 hours instead of buying again. Returns 201 with the number; a warnings array is added when part of the request did not take (for example the AI agent could not be assigned, or the setup fee could not be confirmed). The Try it panel can't send the Idempotency-Key header — use curl for purchases. Errors: 400 idempotency_key_required, 400 validation_error (unknown field, or an agent that is not yours or is archived), 402 insufficient_funds, 403 trial_purchase_not_allowed (trial organizations can't buy via the API unless exempted; contact support), 409 number_unavailable, 409 organization_not_ready, 409 idempotency_key_in_progress / idempotency_key_outcome_unknown, 422 regulatory_bundle_required (the number's country needs an approved regulatory bundle — set it up in the dashboard — or a bundle is still under review), 422 idempotency_key_reused, 500 storage_failed (the number was released), 500 server_error, 502 twilio_error (carrier failure; details.twilio_code), and 502 purchase_incomplete (contact support; a retry replays it). 400, 404 and 422 responses are stored under the key for 24 hours: after fixing the cause, retry with a NEW Idempotency-Key (402, 403 and 409 are not stored). Error details: twilio_code, number_released and phone_number_sid when present; insufficient_funds adds available_cents, required_cents and currency; organization_not_ready adds reason.

Request Body

NameTypeRequiredDescription
phone_numberstring
Required
E.164 number to buy, from the search results.
friendly_namestring
Optional
A label for the number, up to 64 characters.
inbound_agent_iduuid
Optional
AI agent that answers inbound calls.
outbound_agent_iduuid
Optional
AI agent that places outbound calls from this number.

Example Request

bash
# Generate a fresh UUID for every purchase (e.g. uuidgen)
curl -X POST https://app.aivi.io/ignite/api/v1/phone_numbers \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Idempotency-Key: <unique-key-per-purchase>" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155550123",
    "friendly_name": "Support Line",
    "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000"
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "phone_number": "+14155550123",
    "friendly_name": "Support Line",
    "capabilities": {
      "voice": true,
      "sms": true,
      "mms": true,
      "fax": false
    },
    "is_default": false,
    "is_active": true,
    "ai_enabled": true,
    "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "outbound_agent_id": null,
    "forward_to": null,
    "assigned_to": null,
    "created_at": "2026-10-04T09:15:00Z",
    "updated_at": "2026-10-04T09:15:00Z"
  }
}
PATCH

Update a Number

https://app.aivi.io/ignite/api/v1/phone_numbers/:id
phone_numbers:write

Partially update a phone number. Send only the fields to change; at least one is required, and unknown fields are a 400. inbound_agent_id and outbound_agent_id must be non-archived agents of your organization (otherwise 400 validation_error); send null to unassign. The number is routed to the agent's voice provider when an agent changes. A warnings array is added when part of the update did not take (for example the number could not be routed to the voice provider yet); the saved fields are kept. Errors: 400 validation_error, 404 not_found (also for a number that was released), 409 phone_number_provider_conflict (the inbound and outbound agents use different call providers; a number serves one provider), 500 server_error, 502 twilio_error (carrier failure; details.twilio_code).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The phone number ID

Request Body

NameTypeRequiredDescription
friendly_namestring
Optional
A label for the number, up to 64 characters.
inbound_agent_iduuid
Optional
AI agent that answers inbound calls; null to unassign.
outbound_agent_iduuid
Optional
AI agent that places outbound calls; null to unassign.
forward_tostring
Optional
E.164 number to forward inbound calls to; null to stop forwarding.

Example Request

bash
curl -X PATCH https://app.aivi.io/ignite/api/v1/phone_numbers/880e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "friendly_name": "Support Line (after hours)",
    "outbound_agent_id": "770e8400-e29b-41d4-a716-446655440000"
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "phone_number": "+14155550123",
    "friendly_name": "Support Line (after hours)",
    "capabilities": {
      "voice": true,
      "sms": true,
      "mms": true,
      "fax": false
    },
    "is_default": false,
    "is_active": true,
    "ai_enabled": true,
    "inbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "outbound_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "forward_to": null,
    "assigned_to": null,
    "created_at": "2026-10-04T09:15:00Z",
    "updated_at": "2026-10-04T09:15:00Z"
  },
  "warnings": [
    "Could not route the number to the agent's voice provider; it will be retried on the next update."
  ]
}
DELETE

Release a Number

https://app.aivi.io/ignite/api/v1/phone_numbers/:id
phone_numbers:purchase

Release a phone number. A number AIVI bought for you is released back to the carrier and cannot be recovered; action is released. A number you imported from your own Twilio account is only disconnected from AIVI and stays in your account; action is disconnected. Either way the number is deactivated and any agent assignments are cleared. Errors: 404 not_found (also for a number that was already released), 409 organization_not_ready (the organization's telephony is not set up; nothing was changed), 500 server_error (the message says whether the number was already released; contact support if so), 502 twilio_error (carrier failure; nothing was changed; details.twilio_code).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The phone number ID

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/phone_numbers/880e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "880e8400-e29b-41d4-a716-446655440000",
    "action": "released"
  }
}

AI Agents

AI agents are the configured voice agents your organization uses for inbound and outbound calls. Each agent has a system prompt, greeting, voice, language, and LLM model. You can list, create, update, duplicate, archive and permanently delete agents through the API.

Saving an agent queues background work that provisions it with the voice provider. The agent object reports the outcome in sync_status (pending, synced, failed or skipped), sync_stage (the stage that last ran or failed) and sync_error. Provisioning after a create or duplicate happens automatically; every PATCH re-queues the provider sync, so sync_status briefly shows pending after each one; poll GET /ai_agents/:id to see it settle.

llm_provider and model_name are read-only and cannot be set through the API. New and duplicated agents start as draft. Set status to active to place calls: POST /calls returns 409 agent_not_active for an agent that is not active, and, once it is active, 409 agent_not_ready until the voice provider assistant is provisioned (usually seconds).

A phone number serves one call provider: assigning one that another agent uses on a different provider returns 409 phone_number_provider_conflict (create and update). An archived agent cannot be updated until a PATCH restores it with a status; any other update returns 409 agent_archived. Create and duplicate accept an optional Idempotency-Key header, so a retried request does not create a second agent.

DELETE archives an agent by default: its phone numbers are unbound, its provider assistant is removed, and its call history is kept. Archived agents are left out of the list unless you pass ?status=archived. Pass ?permanent=true to delete for good, which is refused while the agent has call history or an IVR route points at it.

Agents are identified by their id, which is the value you pass as agent_id when initiating an outbound call via POST /calls.

The AI Agent Object

json
{
  "id": "770e8400-e29b-41d4-a716-446655440000",
  "organization_id": "440e8400-e29b-41d4-a716-446655440000",
  "name": "Support Agent",
  "description": null,
  "status": "active",
  "call_direction": "both",
  "voice_provider": "cartesia",
  "voice_id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
  "language": "en-US",
  "llm_provider": "openai",
  "model_name": "gpt-4.1-mini",
  "system_prompt": "You are a helpful support agent for Acme.",
  "greeting_message": "Thanks for calling Acme.",
  "phone_number_id": "880e8400-e29b-41d4-a716-446655440000",
  "sync_status": "synced",
  "sync_stage": null,
  "sync_error": null,
  "phone_number_ids": [
    "880e8400-e29b-41d4-a716-446655440000"
  ],
  "created_at": "2026-10-03T12:00:00Z",
  "updated_at": "2026-10-03T12:00:00Z"
}

status is one of draft, active, inactive or archived. The object also carries every writable configuration field (transfer, SMS/email, human takeover and video settings) except agent_functions, which is write-only. The example above is abridged.

GET

List AI Agents

https://app.aivi.io/ignite/api/v1/ai_agents
ai_agents:read

Returns the organization's AI agents, paginated.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number (default 1).
page_sizeinteger
Optional
Items per page, 1–100 (default 20).
statusstring
Optional
draft | active | inactive | archived. Omitted: every status except archived.
call_directionstring
Optional
inbound | outbound | both
searchstring
Optional
Case-insensitive name substring match.
is_templateboolean
Optional
Filter to template agents.
sortstring
Optional
created_at|updated_at|name, optional :asc / :desc.

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/ai_agents?status=active" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "organization_id": "440e8400-e29b-41d4-a716-446655440000",
      "name": "Sales Discovery Agent",
      "status": "active",
      "call_direction": "outbound",
      "voice_provider": "cartesia",
      "language": "en-US",
      "llm_provider": "openai",
      "is_template": false,
      "is_default": true,
      "created_at": "2026-03-15T12:00:00Z",
      "updated_at": "2026-04-22T16:14:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get AI Agent

https://app.aivi.io/ignite/api/v1/ai_agents/:id
ai_agents:read

Retrieve a single AI agent by its unique ID. Returns the agent's full configuration including system prompt, greetings, voice/LLM settings, and transfer rules.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The AI agent ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/ai_agents/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "name": "Sales Discovery Agent",
    "description": "Outbound discovery calls for inbound leads.",
    "status": "active",
    "call_direction": "outbound",
    "voice_provider": "cartesia",
    "voice_id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
    "language": "en-US",
    "llm_provider": "openai",
    "model_name": "gpt-4.1-mini",
    "system_prompt": "You are a friendly AIVI sales rep…",
    "greeting_message": "Hi, this is Alex from AIVI — got a minute?",
    "greeting_message_inbound": "Thanks for calling AIVI. How can I help?",
    "custom_instructions": null,
    "phone_number_id": "880e8400-e29b-41d4-a716-446655440000",
    "recording_enabled": true,
    "transcription_enabled": true,
    "is_template": false,
    "is_default": true,
    "transfer_number": "+12027779999",
    "sms_enabled": false,
    "created_at": "2026-03-15T12:00:00Z",
    "updated_at": "2026-04-22T16:14:00Z"
  }
}
POST

Create AI Agent

https://app.aivi.io/ignite/api/v1/ai_agents
ai_agents:write

Create an AI agent. name and system_prompt are required; every other field is optional. Returns 201 with the new agent. The agent starts as a draft unless you send a status; provisioning with the voice provider runs in the background, so sync_status starts as pending. Set status to active to place calls (POST /calls returns 409 agent_not_active otherwise); once active, POST /calls returns 409 agent_not_ready until the voice provider assistant is provisioned (usually seconds). llm_provider and model_name cannot be set. A warnings array is added to the response when part of the save did not take (for example a phone number could not be attached). An unknown or read-only field, a phone number or email sender from another organization, or an out-of-range value is a 400 validation_error. A phone number already used by an agent on another call provider is a 409 phone_number_provider_conflict. Accepts an optional Idempotency-Key header: a retry with the same key replays the first response instead of creating a second agent.

Request Body

NameTypeRequiredDescription
namestring
Required
1–200 characters.
descriptionstring | null
Optional
Up to 5000 characters.
statusstring
Optional
draft | active | inactive. Archiving is done with DELETE, not by sending archived.
call_directionstring
Optional
inbound | outbound | both (outbound is legacy and behaves like both).
languagestring
Optional
One of multi, en-US, en-GB, en-AU, en-IN, es-ES, es-MX, fr-FR, de-DE, pt-BR, pt-PT, it-IT, nl-NL, pl-PL, ja-JP, ko-KR, zh-CN, hi-IN, ru-RU, sv-SE, da-DK.
voice_providerstring
Optional
cartesia | elevenlabs | fishaudio. Changing the provider resets voice_id unless you send one.
voice_idstring | null
Optional
Voice id at the chosen voice provider (max 100 characters).
system_promptstring
Required
The agent instructions. Must not be empty.
greeting_messagestring | null
Optional
Opening line for outbound calls.
greeting_message_inboundstring | null
Optional
Opening line for inbound calls.
consent_scriptstring | null
Optional
Recording-consent statement spoken at the start of a call.
is_defaultboolean
Optional
Make this the default agent.
transfer_rotationstring
Optional
round_robin | percentage | priority | dynamic | simultaneous
transfer_typestring
Optional
cold | warm
transfer_caller_id_modestring
Optional
agent_number | contact_number
transfer_whisper_messagestring | null
Optional
Spoken privately to the transfer target on a warm transfer.
transfer_notify_unansweredboolean
Optional
Tell the caller when nobody answered the transfer.
transfer_announcementstring | null
Optional
Spoken to the caller before transferring.
transfer_failure_messagestring | null
Optional
Spoken to the caller when the transfer fails.
transfer_answer_timeout_secondsinteger
Optional
15–120.
transfer_connect_timeout_secondsinteger
Optional
15–180.
sms_enabledboolean
Optional
Let the agent handle SMS conversations.
email_enabledboolean
Optional
Let the agent handle email conversations.
email_sender_iduuid | null
Optional
An email sender of your organization. Another organization’s sender is a 400.
sms_human_takeover_enabledboolean
Optional
Pause the agent on SMS when a human replies.
sms_human_takeover_modestring
Optional
window | indefinite
sms_human_takeover_hoursinteger
Optional
1–720.
email_human_takeover_enabledboolean
Optional
Pause the agent on email when a human replies.
email_human_takeover_modestring
Optional
window | indefinite
email_human_takeover_hoursinteger
Optional
1–720.
video_enabledboolean
Optional
Enable the live video avatar.
live_avatar_idstring | null
Optional
Live avatar identifier.
live_avatar_preview_urlstring | null
Optional
URL of the avatar preview image.
video_primary_colorstring
Optional
Hex color, #RRGGBB or #RRGGBBAA.
video_positionstring | null
Optional
bottom-right | bottom-left | top-right | top-left
video_cta_labelstring
Optional
Up to 100 characters.
video_brand_namestring | null
Optional
Up to 100 characters.
video_launcher_stylestring
Optional
pill | bubble | avatar
video_subtitlestring | null
Optional
Up to 140 characters.
video_aspect_ratiostring
Optional
16:9 | 9:16 | 1:1
video_themestring
Optional
dark | light
video_offset_xinteger
Optional
Horizontal launcher offset in pixels.
video_offset_yinteger
Optional
Vertical launcher offset in pixels.
video_greeting_messagestring | null
Optional
Greeting shown in the video widget.
transfer_targetsobject[]
Optional
Up to 50 transfer destinations. Each is { userId?, phoneNumber?, sipUri?, sipTrunkId?, label?, percentage?, description?, isCustom? }; name exactly one of userId, phoneNumber or sipUri. Unknown keys are a 400. Replaces the list.
transfer_global_rulesobject[]
Optional
Up to 50 condition-routing rules. Each is { id?, condition, target, announcement?, failureMessage? } where target has the same shape as a transfer_targets entry. Replaces the list.
agent_functionsobject[]
Optional
Up to 100 tool definitions (names, action types and configs are validated server-side). WRITE-ONLY: never returned in the agent object because tool configs can hold webhook secrets. A PATCH replaces the whole list, so resend every function you want to keep. screen_assist PII masking cannot be changed through the API: it is stripped on create and the stored value is kept on update.
phone_number_idsuuid[]
Optional
Up to 50 phone numbers of your organization that make up the agent’s pool; the first is the primary. Replaces the pool. A number from another organization is a 400 naming the bad ids. Binding a number to an agent switches it into AI mode (its calls go to the voice agent), so a key with ai_agents:write can change how a number answers calls.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/ai_agents \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support Agent",
    "system_prompt": "You are a helpful support agent for Acme.",
    "greeting_message": "Thanks for calling Acme.",
    "phone_number_ids": ["880e8400-e29b-41d4-a716-446655440000"]
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "name": "Support Agent",
    "description": null,
    "status": "draft",
    "call_direction": "both",
    "voice_provider": "cartesia",
    "voice_id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
    "language": "en-US",
    "llm_provider": "openai",
    "model_name": "gpt-4.1-mini",
    "system_prompt": "You are a helpful support agent for Acme.",
    "greeting_message": "Thanks for calling Acme.",
    "phone_number_id": "880e8400-e29b-41d4-a716-446655440000",
    "sync_status": "pending",
    "sync_stage": null,
    "sync_error": null,
    "phone_number_ids": [
      "880e8400-e29b-41d4-a716-446655440000"
    ],
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z"
  }
}
PATCH

Update AI Agent

https://app.aivi.io/ignite/api/v1/ai_agents/:id
ai_agents:write

Partially update an AI agent. Send only the fields to change; at least one is required. The body is strict: unknown or read-only fields (llm_provider, model_name, metadata, sync_*) are a 400. Every PATCH re-syncs the agent with the voice provider, so sync_status briefly shows pending afterwards. agent_functions is write-only and is replaced wholesale. screen_assist PII masking cannot be changed through the API (the stored value is kept). Changing voice_provider resets voice_id to an empty string unless you send voice_id too. phone_number_ids replaces the phone pool. Updating an archived agent returns 409 agent_archived unless the body includes a status (draft, active or inactive), which un-archives it; un-archiving does not restore its phone numbers, so send phone_number_ids as well. A phone number already used by an agent on another call provider is a 409 phone_number_provider_conflict. A warnings array is added to the response when part of the save did not take.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The AI agent ID

Request Body

NameTypeRequiredDescription
namestring
Optional
1–200 characters.
descriptionstring | null
Optional
Up to 5000 characters.
statusstring
Optional
draft | active | inactive. Archiving is done with DELETE, not by sending archived.
call_directionstring
Optional
inbound | outbound | both (outbound is legacy and behaves like both).
languagestring
Optional
One of multi, en-US, en-GB, en-AU, en-IN, es-ES, es-MX, fr-FR, de-DE, pt-BR, pt-PT, it-IT, nl-NL, pl-PL, ja-JP, ko-KR, zh-CN, hi-IN, ru-RU, sv-SE, da-DK.
voice_providerstring
Optional
cartesia | elevenlabs | fishaudio. Changing the provider resets voice_id unless you send one.
voice_idstring | null
Optional
Voice id at the chosen voice provider (max 100 characters).
system_promptstring
Optional
The agent instructions. Must not be empty.
greeting_messagestring | null
Optional
Opening line for outbound calls.
greeting_message_inboundstring | null
Optional
Opening line for inbound calls.
consent_scriptstring | null
Optional
Recording-consent statement spoken at the start of a call.
is_defaultboolean
Optional
Make this the default agent.
transfer_rotationstring
Optional
round_robin | percentage | priority | dynamic | simultaneous
transfer_typestring
Optional
cold | warm
transfer_caller_id_modestring
Optional
agent_number | contact_number
transfer_whisper_messagestring | null
Optional
Spoken privately to the transfer target on a warm transfer.
transfer_notify_unansweredboolean
Optional
Tell the caller when nobody answered the transfer.
transfer_announcementstring | null
Optional
Spoken to the caller before transferring.
transfer_failure_messagestring | null
Optional
Spoken to the caller when the transfer fails.
transfer_answer_timeout_secondsinteger
Optional
15–120.
transfer_connect_timeout_secondsinteger
Optional
15–180.
sms_enabledboolean
Optional
Let the agent handle SMS conversations.
email_enabledboolean
Optional
Let the agent handle email conversations.
email_sender_iduuid | null
Optional
An email sender of your organization. Another organization’s sender is a 400.
sms_human_takeover_enabledboolean
Optional
Pause the agent on SMS when a human replies.
sms_human_takeover_modestring
Optional
window | indefinite
sms_human_takeover_hoursinteger
Optional
1–720.
email_human_takeover_enabledboolean
Optional
Pause the agent on email when a human replies.
email_human_takeover_modestring
Optional
window | indefinite
email_human_takeover_hoursinteger
Optional
1–720.
video_enabledboolean
Optional
Enable the live video avatar.
live_avatar_idstring | null
Optional
Live avatar identifier.
live_avatar_preview_urlstring | null
Optional
URL of the avatar preview image.
video_primary_colorstring
Optional
Hex color, #RRGGBB or #RRGGBBAA.
video_positionstring | null
Optional
bottom-right | bottom-left | top-right | top-left
video_cta_labelstring
Optional
Up to 100 characters.
video_brand_namestring | null
Optional
Up to 100 characters.
video_launcher_stylestring
Optional
pill | bubble | avatar
video_subtitlestring | null
Optional
Up to 140 characters.
video_aspect_ratiostring
Optional
16:9 | 9:16 | 1:1
video_themestring
Optional
dark | light
video_offset_xinteger
Optional
Horizontal launcher offset in pixels.
video_offset_yinteger
Optional
Vertical launcher offset in pixels.
video_greeting_messagestring | null
Optional
Greeting shown in the video widget.
transfer_targetsobject[]
Optional
Up to 50 transfer destinations. Each is { userId?, phoneNumber?, sipUri?, sipTrunkId?, label?, percentage?, description?, isCustom? }; name exactly one of userId, phoneNumber or sipUri. Unknown keys are a 400. Replaces the list.
transfer_global_rulesobject[]
Optional
Up to 50 condition-routing rules. Each is { id?, condition, target, announcement?, failureMessage? } where target has the same shape as a transfer_targets entry. Replaces the list.
agent_functionsobject[]
Optional
Up to 100 tool definitions (names, action types and configs are validated server-side). WRITE-ONLY: never returned in the agent object because tool configs can hold webhook secrets. A PATCH replaces the whole list, so resend every function you want to keep. screen_assist PII masking cannot be changed through the API: it is stripped on create and the stored value is kept on update.
phone_number_idsuuid[]
Optional
Up to 50 phone numbers of your organization that make up the agent’s pool; the first is the primary. Replaces the pool. A number from another organization is a 400 naming the bad ids. Binding a number to an agent switches it into AI mode (its calls go to the voice agent), so a key with ai_agents:write can change how a number answers calls.

Example Request

bash
curl -X PATCH https://app.aivi.io/ignite/api/v1/ai_agents/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "active",
    "greeting_message": "Thanks for calling Acme, how can I help?"
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "name": "Support Agent",
    "description": null,
    "status": "active",
    "call_direction": "both",
    "voice_provider": "cartesia",
    "voice_id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
    "language": "en-US",
    "llm_provider": "openai",
    "model_name": "gpt-4.1-mini",
    "system_prompt": "You are a helpful support agent for Acme.",
    "greeting_message": "Thanks for calling Acme, how can I help?",
    "phone_number_id": "880e8400-e29b-41d4-a716-446655440000",
    "sync_status": "pending",
    "sync_stage": null,
    "sync_error": null,
    "phone_number_ids": [
      "880e8400-e29b-41d4-a716-446655440000"
    ],
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z"
  }
}
POST

Duplicate AI Agent

https://app.aivi.io/ignite/api/v1/ai_agents/:id/duplicate
ai_agents:write

Copy an AI agent of your organization. Returns 201 with the copy. The copy keeps the source's configuration (prompt, greetings, voice, language, call provider, transfer, SMS/email, human takeover, video settings and agent_functions) but gets its own voice provider assistant, provisioned in the background (sync_status starts as pending), and no phone numbers. It starts as a draft and is never the default agent. Set status to active to place calls (POST /calls returns 409 agent_not_active otherwise); once active, POST /calls returns 409 agent_not_ready until the voice provider assistant is provisioned (usually seconds). The body is optional: no body, an empty body or {} gives the copy the default name. Accepts an optional Idempotency-Key header: a retry with the same key replays the first response instead of creating a second copy. System agents cannot be duplicated: 409 system_agent.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The AI agent to copy

Request Body

NameTypeRequiredDescription
namestring
Optional
Name of the copy, 1–200 characters. Defaults to "<source name> (Copy)".

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/ai_agents/770e8400-e29b-41d4-a716-446655440000/duplicate \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Support Agent (EU)"}'

Response
201

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "organization_id": "440e8400-e29b-41d4-a716-446655440000",
    "name": "Support Agent (EU)",
    "description": null,
    "status": "draft",
    "call_direction": "both",
    "voice_provider": "cartesia",
    "voice_id": "d46abd1d-2d02-43e8-819f-51fb652c1c61",
    "language": "en-US",
    "llm_provider": "openai",
    "model_name": "gpt-4.1-mini",
    "system_prompt": "You are a helpful support agent for Acme.",
    "greeting_message": "Thanks for calling Acme.",
    "phone_number_id": null,
    "sync_status": "pending",
    "sync_stage": null,
    "sync_error": null,
    "phone_number_ids": [],
    "created_at": "2026-10-03T12:00:00Z",
    "updated_at": "2026-10-03T12:00:00Z"
  }
}
DELETE

Delete AI Agent

https://app.aivi.io/ignite/api/v1/ai_agents/:id
ai_agents:delete

Archive an AI agent (default) or delete it permanently. Archiving unbinds the agent's phone numbers, removes its provider assistant, clears is_default and keeps its history; archiving an archived agent again finishes any step an earlier attempt left undone; the archived agent is returned with status archived and is hidden from the list unless you pass ?status=archived. With ?permanent=true the agent is deleted for good and the response is { id, deleted: true }. Both modes return 409 system_agent for a system agent. Permanent delete also returns 409 agent_has_call_history when any call, AI-agent call or SMS thread references the agent (archive it instead), and 409 agent_in_use when an IVR route sends calls to it. These checks run before anything is changed.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The AI agent ID

Query Parameters

NameTypeRequiredDescription
permanentstring
Optional
Exactly "true" or "false" (any other value is a 400). "true": delete for good (409 agent_has_call_history, agent_in_use). "false" or omitted: archive.

Example Request

bash
curl -X DELETE "https://app.aivi.io/ignite/api/v1/ai_agents/770e8400-e29b-41d4-a716-446655440000?permanent=true" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}

Experts

An Expert (also called a Skill) is a governed, versioned procedure built in Agent Foundry — an ordered SOP whose steps call real tools. Experts are owned, scored against past calls, and human-certified before they can act. These endpoints let you list them, read their SOP, and run one programmatically.

Invoking an Expert runs the same executor the voice agent uses, so an Expert can only ever do what your agents could already do — now versioned and auditable. Every invocation is logged.

Invoke is intended for Experts that don't depend on live-call state. Foundry Experts can be gated on conditions that are only established during a phone call (identity verified, one-time passcode confirmed). An API request is not a call, so there is no per-call state to check those against and a gated Expert will refuse — returning status: "gate_blocked" with the reason. This is the security gate working as designed, not an error. Pass call_id when you are acting in the context of a real call to evaluate those gates.

Certification controls whether it really acts. An Expert with cert_status: "live" performs its steps for real. One that is certified or validated runs in shadow: it reports what it would have done and performs no side effects (status: "shadow"). Anything else is not invokable. Always check the returned status — a 200 means the request succeeded, not that the Expert acted.

Invocation Status Values

NameTypeRequiredDescription
donestring
Optional
The Expert ran for real and completed its SOP.
shadowstring
Optional
Dry run — it reported what it would do; no side effects (certified/validated, not yet live).
escalatestring
Optional
The SOP reached a step that hands off to a human; execution stopped there.
gate_blockedstring
Optional
A scope gate or prerequisite was not satisfied (e.g. identity not verified on this call). Nothing ran.
blockedstring
Optional
The Expert is not certified, so it is not invokable.
errorstring
Optional
A step failed. The result field carries the reason.
GET

List Experts

https://app.aivi.io/ignite/api/v1/experts
experts:read

Retrieve a paginated list of the Experts (Skills) in your organization, newest first. Use cert_status to find the ones that are invokable.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
searchstring
Optional
Case-insensitive match on the Expert name (1-200 characters)
cert_statusstring
Optional
Filter by lifecycle state: draft, pending_cert, validated, certified, live, rejected

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/experts?cert_status=live" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "name": "Schedule Callback",
      "description": "Books a callback for the caller at a time they choose.",
      "cert_status": "live",
      "intent": "Use when the caller asks to be called back at a different time.",
      "skill_name": "schedule-callback",
      "skill_version": "v1",
      "is_human_gated": false,
      "replay_score": 0.82,
      "agent_id": "660e8400-e29b-41d4-a716-446655440000",
      "scope_id": null,
      "created_at": "2026-06-30T12:00:00Z",
      "updated_at": "2026-07-14T09:20:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Expert

https://app.aivi.io/ignite/api/v1/experts/:id
experts:read

Retrieve a single Expert, including its prerequisites (the conditions that must hold before it runs) and its procedure (the ordered SOP steps).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The Expert ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/experts/990e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "name": "Schedule Callback",
    "description": "Books a callback for the caller at a time they choose.",
    "cert_status": "live",
    "intent": "Use when the caller asks to be called back at a different time.",
    "skill_name": "schedule-callback",
    "skill_version": "v1",
    "is_human_gated": false,
    "replay_score": 0.82,
    "agent_id": "660e8400-e29b-41d4-a716-446655440000",
    "scope_id": null,
    "created_at": "2026-06-30T12:00:00Z",
    "updated_at": "2026-07-14T09:20:00Z",
    "prerequisites": [
      {
        "type": "state",
        "key": "identity_verified",
        "value": true
      }
    ],
    "procedure": [
      {
        "n": 1,
        "text": "Ask the caller what day and time suits them."
      },
      {
        "n": 2,
        "text": "Book the callback.",
        "tool_ref": {
          "name": "schedule_callback"
        }
      }
    ]
  }
}
POST

Invoke Expert

https://app.aivi.io/ignite/api/v1/experts/:id/invoke
experts:invoke

Run an Expert once and return the outcome. Executes through the same runtime the voice agent uses. Read the Overview above first — certification decides whether it acts for real, and gated Experts refuse outside a live call.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The Expert ID

Request Body

NameTypeRequiredDescription
argsobject
Optional
Inputs for the SOP steps (e.g. name, callback_datetime). Each step takes what it needs; see the Expert's procedure via Get Expert.
contact_iduuid
Optional
The contact to act on. Required by steps that read or write contact data (identity checks, SMS, field updates).
call_iduuid
Optional
An existing call to run in the context of. Supply this to evaluate gates/prerequisites against that call's state; omit it and gated Experts will refuse.
ai_agent_iduuid
Optional
The AI agent whose configuration the steps should use, when relevant.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/experts/990e8400-e29b-41d4-a716-446655440000/invoke \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "args": {
      "callback_datetime": "tomorrow at 2pm",
      "callback_timezone": "America/New_York"
    }
  }'

Response
200

json
{
  "success": true,
  "data": {
    "status": "done",
    "expert": {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "name": "Schedule Callback",
      "skill_name": "schedule-callback",
      "skill_version": "v1"
    },
    "result": "Callback scheduled for Thursday at 2:00 PM Eastern.",
    "score": 0.82,
    "steps": [
      {
        "n": 1,
        "text": "Ask the caller what day and time suits them.",
        "outcome": "no action"
      },
      {
        "n": 2,
        "text": "Book the callback.",
        "outcome": "schedule_callback: booked for 2026-07-24T18:00:00Z"
      }
    ]
  }
}

Contacts

Contacts represent people in your CRM. Each contact belongs to an organization and is identified by a unique ID. Every contact must carry at least one identifier: a phone number or an email address.

A contact holds a list of typed phone numbers and a list of typed email addresses, returned as phones and emails and ordered by position. Exactly one entry in each list is the primary.

The phone, secondary_phone and email scalars are still returned on every response and are still writable, but they are now derived mirrors of those lists — phone is the primary number, secondary_phone the highest-ranked non-primary number, and email the primary address. secondary_phone is deprecated: it is no longer a field of its own, only a projection of the list.

Write the whole list with phones / emails, or write the mirrors with phone / secondary_phone / email — but never both in the same request, which returns 400. On PUT a supplied list replacesthe contact's existing list.

The Contact Object

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "first_name": "Jane",
  "last_name": "Smith",
  "full_name": "Jane Smith",
  "email": "jane@example.com",
  "phone": "+12025551234",
  "secondary_phone": "+12025559876",
  "address": {
    "street": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "zip": "78701"
  },
  "status": "active",
  "tags": [
    "vip",
    "enterprise"
  ],
  "custom_fields": {
    "company": "Acme Inc"
  },
  "assigned_to": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "source": "api",
  "last_contacted_at": "2026-02-20T14:30:00Z",
  "created_at": "2026-01-15T09:00:00Z",
  "updated_at": "2026-02-20T14:30:00Z",
  "phones": [
    {
      "id": "9a1c7f52-3b0e-4f1a-9a2d-1c7b5e0f4a11",
      "phone": "+12025551234",
      "type": "mobile",
      "is_primary": true,
      "position": 0,
      "is_dnc": false,
      "dnc_added_at": null,
      "dnc_reason": null
    },
    {
      "id": "b4e6d310-72c8-4a5f-8e39-6d2f0a9c1b73",
      "phone": "+12025559876",
      "type": "work",
      "is_primary": false,
      "position": 1,
      "is_dnc": true,
      "dnc_added_at": "2026-03-02T09:15:00Z",
      "dnc_reason": "caller_request"
    }
  ],
  "emails": [
    {
      "id": "c7f2a9d4-5e18-4c60-b3a7-8f1d2e6b0c95",
      "email": "jane@example.com",
      "type": "personal",
      "is_primary": true,
      "position": 0,
      "is_dnc": false,
      "dnc_added_at": null,
      "dnc_reason": null
    }
  ]
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique identifier (read-only)
first_namestring
Optional
First name
last_namestring
Optional
Last name
full_namestring
Optional
Computed full name (read-only)
emailstring
Optional
Mirror of the primary entry in emails. Writable — writing it updates that entry
phonestring
Optional
Mirror of the primary entry in phones, E.164 format (e.g. +12025551234). Writable — writing it updates that entry
secondary_phonestring
Optional
DEPRECATED. Mirror of the highest-ranked non-primary entry in phones, E.164 format. Still returned and still writable, but it is a projection of the list, not a field of its own — read phones instead
phonesobject[]
Optional
The contact’s phone list, ordered by position. Each entry is { id, phone, type, is_primary, position, is_dnc, dnc_added_at, dnc_reason }; type is one of mobile, home, work, main, fax, other. Exactly one entry has is_primary: true. is_dnc is READ-ONLY and scoped to that number alone — set it with POST /contacts/:id/phones/:point_id/dnc, not by writing this array
emailsobject[]
Optional
The contact’s email list, ordered by position. Each entry is { id, email, type, is_primary, position, is_dnc, dnc_added_at, dnc_reason }; type is one of personal, work, other. Exactly one entry has is_primary: true. is_dnc is READ-ONLY — see the phones note
addressobject
Optional
Address object (street, city, state, zip, country)
statusstring
Optional
One of: active, inactive, scrubbed, followup, deceased
tagsstring[]
Optional
Array of tag strings
custom_fieldsobject
Optional
Arbitrary key-value pairs
assigned_touuid
Optional
ID of the assigned user
sourcestring
Optional
Where the contact originated from
last_contacted_attimestamp
Optional
Last interaction time (read-only)
created_attimestamp
Optional
Creation time (read-only)
updated_attimestamp
Optional
Last update time (read-only)
GET

List Contacts

https://app.aivi.io/ignite/api/v1/contacts
contacts:read

Retrieve a paginated list of contacts. Supports filtering by status, tags, search query, and more.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
statusstring
Optional
Filter by status (active, inactive, scrubbed, followup, deceased)
tagsstring
Optional
Filter by tags (comma-separated). Matches contacts with any of the specified tags
searchstring
Optional
Case-insensitive substring match across all contact-facing fields: name, source, address, tags, all custom field values, and every phone number and email address in the contact’s phones/emails lists (phone terms also match on digits alone).
emailstring
Optional
Filter by exact email address (case-insensitive). Matches the email mirror only — use search to reach a non-primary address
phonestring
Optional
Filter by exact phone number (E.164 format). Matches the phone and secondary_phone mirrors and any number in the contact’s phones list
assigned_touuid
Optional
Filter by assigned user ID
sortstring
Optional
Sort field and direction (e.g. created_at:desc). Allowed fields: created_at, updated_at, first_name, last_name, email, phone, status (phone and email sort on the mirror columns) Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/contacts?page=1&page_size=10&status=active" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "first_name": "Jane",
      "last_name": "Smith",
      "full_name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "+12025551234",
      "secondary_phone": "+12025559876",
      "address": null,
      "status": "active",
      "tags": [
        "vip"
      ],
      "custom_fields": {},
      "assigned_to": null,
      "source": "api",
      "last_contacted_at": null,
      "created_at": "2026-01-15T09:00:00Z",
      "updated_at": "2026-01-15T09:00:00Z",
      "phones": [
        {
          "id": "9a1c7f52-3b0e-4f1a-9a2d-1c7b5e0f4a11",
          "phone": "+12025551234",
          "type": "mobile",
          "is_primary": true,
          "position": 0,
          "is_dnc": false,
          "dnc_added_at": null,
          "dnc_reason": null
        },
        {
          "id": "b4e6d310-72c8-4a5f-8e39-6d2f0a9c1b73",
          "phone": "+12025559876",
          "type": "work",
          "is_primary": false,
          "position": 1,
          "is_dnc": false,
          "dnc_added_at": null,
          "dnc_reason": null
        }
      ],
      "emails": [
        {
          "id": "c7f2a9d4-5e18-4c60-b3a7-8f1d2e6b0c95",
          "email": "jane@example.com",
          "type": "personal",
          "is_primary": true,
          "position": 0,
          "is_dnc": false,
          "dnc_added_at": null,
          "dnc_reason": null
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 10,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Contact

https://app.aivi.io/ignite/api/v1/contacts/:id
contacts:read

Retrieve a single contact by its unique ID.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jane",
    "last_name": "Smith",
    "full_name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+12025551234",
    "secondary_phone": "+12025559876",
    "address": {
      "street": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    },
    "status": "active",
    "tags": [
      "vip",
      "enterprise"
    ],
    "custom_fields": {
      "company": "Acme Inc"
    },
    "assigned_to": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "source": "api",
    "last_contacted_at": "2026-02-20T14:30:00Z",
    "created_at": "2026-01-15T09:00:00Z",
    "updated_at": "2026-02-20T14:30:00Z",
    "phones": [
      {
        "id": "9a1c7f52-3b0e-4f1a-9a2d-1c7b5e0f4a11",
        "phone": "+12025551234",
        "type": "mobile",
        "is_primary": true,
        "position": 0
      },
      {
        "id": "b4e6d310-72c8-4a5f-8e39-6d2f0a9c1b73",
        "phone": "+12025559876",
        "type": "work",
        "is_primary": false,
        "position": 1
      }
    ],
    "emails": [
      {
        "id": "c7f2a9d4-5e18-4c60-b3a7-8f1d2e6b0c95",
        "email": "jane@example.com",
        "type": "personal",
        "is_primary": true,
        "position": 0
      }
    ]
  }
}
POST

Create Contact

https://app.aivi.io/ignite/api/v1/contacts
contacts:write

Create a new contact. At least one identifier is required: phone, email, or a non-empty phones/emails list. Phone numbers must be in E.164 format. Sending phones together with phone/secondary_phone (or emails together with email) is a 400. By default, requests with a phone or email already in use return 409. Set trigger_flows_on_duplicates to true to instead get a 200 with the existing contact and re-fire any contact_created workflows against it.

Request Body

NameTypeRequiredDescription
phonestring
Optional
Phone in E.164 format (e.g. +12025551234). Becomes the primary entry of the contact’s phone list. Cannot be combined with phones
emailstring
Optional
Email address. Becomes the primary entry of the contact’s email list. Cannot be combined with emails
first_namestring
Optional
First name
last_namestring
Optional
Last name
secondary_phonestring
Optional
DEPRECATED — prefer phones. A second number in E.164 format, stored as a non-primary entry of the phone list. Cannot be combined with phones
addressobject
Optional
Address object
statusstring
Optional
Contact status (active, inactive, scrubbed, followup, deceased)
tagsstring[]
Optional
Comma-separated tags
custom_fieldsobject
Optional
Custom key-value pairs
assigned_touuid
Optional
User ID to assign the contact to
sourcestring
Optional
Origin source of the contact
trigger_flows_on_duplicatesboolean
Optional
Default false. When true, a duplicate phone/email returns 200 with the existing contact and re-fires contact_created workflow triggers against it instead of returning 409
phonesobject[]
Optional
The contact’s full phone list, max 20 entries. Each entry is { phone, type?, is_primary? } — phone in E.164, type one of mobile (default), home, work, main, fax, other. The first entry becomes the primary unless another sets is_primary: true. Cannot be combined with phone or secondary_phone
emailsobject[]
Optional
The contact’s full email list, max 20 entries. Each entry is { email, type?, is_primary? } — type one of personal (default), work, other. The first entry becomes the primary unless another sets is_primary: true. Cannot be combined with email

Example Request

bash
# Scalars — phone becomes the primary, secondary_phone a second entry
curl -X POST https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+12025551234",
    "email": "jane@example.com",
    "first_name": "Jane",
    "last_name": "Smith",
    "status": "active",
    "tags": ["vip"],
    "trigger_flows_on_duplicates": false
  }'

# Typed lists — a mobile primary plus a work number. Do NOT also send
# "phone"/"secondary_phone" here: mixing the two forms is a 400.
curl -X POST https://app.aivi.io/ignite/api/v1/contacts \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jane",
    "last_name": "Smith",
    "phones": [
      { "phone": "+12025551234", "type": "mobile", "is_primary": true },
      { "phone": "+12025559876", "type": "work" }
    ],
    "emails": [
      { "email": "jane@example.com", "type": "personal" }
    ]
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jane",
    "last_name": "Smith",
    "full_name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+12025551234",
    "secondary_phone": "+12025559876",
    "address": null,
    "status": "active",
    "tags": [
      "vip"
    ],
    "custom_fields": {},
    "assigned_to": null,
    "source": null,
    "last_contacted_at": null,
    "created_at": "2026-02-26T12:00:00Z",
    "updated_at": "2026-02-26T12:00:00Z",
    "phones": [
      {
        "id": "9a1c7f52-3b0e-4f1a-9a2d-1c7b5e0f4a11",
        "phone": "+12025551234",
        "type": "mobile",
        "is_primary": true,
        "position": 0
      },
      {
        "id": "b4e6d310-72c8-4a5f-8e39-6d2f0a9c1b73",
        "phone": "+12025559876",
        "type": "work",
        "is_primary": false,
        "position": 1
      }
    ],
    "emails": [
      {
        "id": "c7f2a9d4-5e18-4c60-b3a7-8f1d2e6b0c95",
        "email": "jane@example.com",
        "type": "personal",
        "is_primary": true,
        "position": 0
      }
    ]
  }
}
PUT

Update Contact

https://app.aivi.io/ignite/api/v1/contacts/:id
contacts:write

Update an existing contact. Provide at least one field; only the fields you include are changed. Set a field to null to clear it. phones/emails are the exception to the additive merge rules: a supplied list REPLACES the contact's existing list ([] clears it), and it cannot be combined with phone/secondary_phone or email in the same request (that is a 400).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Request Body

NameTypeRequiredDescription
first_namestring
Optional
First name
last_namestring
Optional
Last name
emailstring | null
Optional
Email address — updates the primary entry of the email list (null to clear). Cannot be combined with emails
phonestring | null
Optional
Phone in E.164 format — updates the primary entry of the phone list (null to clear). Cannot be combined with phones
secondary_phonestring | null
Optional
DEPRECATED — prefer phones. Updates the highest-ranked non-primary entry of the phone list (null to clear). Cannot be combined with phones
addressobject | null
Optional
Address object (null to clear)
statusstring
Optional
Contact status
tagsstring[]
Optional
Add tags (merged with existing)
custom_fieldsobject
Optional
Merge custom fields (additive)
assigned_touuid | null
Optional
User ID or null to unassign
sourcestring | null
Optional
Origin source
phonesobject[]
Optional
REPLACES the contact’s phone list, max 20 entries of { phone, type?, is_primary? }. Not nullable — send [] to clear it. The first entry becomes the primary unless another sets is_primary: true. Cannot be combined with phone or secondary_phone
emailsobject[]
Optional
REPLACES the contact’s email list, max 20 entries of { email, type?, is_primary? }. Not nullable — send [] to clear it. The first entry becomes the primary unless another sets is_primary: true. Cannot be combined with email

Example Request

bash
curl -X PUT https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Janet",
    "tags": ["vip", "enterprise"]
  }'

# Replace the whole phone list — the mobile stays primary, the work number
# is re-ordered after it, and any other number the contact had is dropped.
curl -X PUT https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "phones": [
      { "phone": "+12025551234", "type": "mobile", "is_primary": true },
      { "phone": "+12025559876", "type": "work" }
    ]
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Janet",
    "last_name": "Smith",
    "full_name": "Janet Smith",
    "email": "jane@example.com",
    "phone": "+12025551234",
    "secondary_phone": "+12025559876",
    "address": {
      "street": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    },
    "status": "active",
    "tags": [
      "vip",
      "enterprise"
    ],
    "custom_fields": {
      "company": "Acme Inc"
    },
    "assigned_to": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "source": "api",
    "last_contacted_at": "2026-02-20T14:30:00Z",
    "created_at": "2026-01-15T09:00:00Z",
    "updated_at": "2026-02-26T12:00:00Z",
    "phones": [
      {
        "id": "9a1c7f52-3b0e-4f1a-9a2d-1c7b5e0f4a11",
        "phone": "+12025551234",
        "type": "mobile",
        "is_primary": true,
        "position": 0
      },
      {
        "id": "b4e6d310-72c8-4a5f-8e39-6d2f0a9c1b73",
        "phone": "+12025559876",
        "type": "work",
        "is_primary": false,
        "position": 1
      }
    ],
    "emails": [
      {
        "id": "c7f2a9d4-5e18-4c60-b3a7-8f1d2e6b0c95",
        "email": "jane@example.com",
        "type": "personal",
        "is_primary": true,
        "position": 0
      }
    ]
  }
}
DELETE

Delete Contact

https://app.aivi.io/ignite/api/v1/contacts/:id
contacts:delete

Soft-delete a contact. The contact will no longer appear in list results but its data is retained internally.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}
GET

List DNC Records

https://app.aivi.io/ignite/api/v1/contacts/:id/dnc
contacts:read

Return the full Do Not Contact audit trail for a contact (active and revoked rows, newest first). Each row carries `channel` (which medium it suppresses: all, voice, sms or email) and its scope — `scope_kind` (contact, phone or email) plus `scope_value`, the number or address it was recorded against. scope_kind is authoritative: a point-scoped record keeps suppressing its value even after that number is deleted, at which point contact_phone_id becomes null. The contact's is_dnc flag is true when at least one row is unrevoked on ANY channel, so a texts-only opt-out still sets it. Outreach endpoints return 403 with code DNC_BLOCKED only when the channel they use is actually suppressed.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "a1b2c3d4-...",
      "contact_id": "550e8400-e29b-41d4-a716-446655440000",
      "reason": "litigator",
      "source": "aivi_insights",
      "added_by_user_id": null,
      "call_id": null,
      "message_id": null,
      "moment_id": null,
      "notes": "Phone Is Litigator = Yes from RRDB Lead Authentication API",
      "evidence": {
        "phone_is_litigator": "1"
      },
      "added_at": "2026-05-04T17:30:00Z",
      "revoked_at": null,
      "revoked_by_user_id": null,
      "revoked_reason": null
    }
  ]
}
POST

Mark Contact DNC

https://app.aivi.io/ignite/api/v1/contacts/:id/dnc
contacts:write

Manually add a Do Not Contact audit record. Outreach over the named channel is blocked immediately. Idempotent per (contact, reason, channel) — repeated calls return the existing record, and an sms-scoped record does not block a later all-channel one. Only channel=all cancels the contact's active workflow enrollments and revokes their outstanding TCPA consents; a narrower channel leaves both untouched, because that consent revocation is never reversed. Reasons litigator, caller_request, sms_stop, unsubscribe, and crm_sync are reserved for AIVI's automated paths (crm_sync marks a record mirrored from a connected CRM such as Bonzo); API clients may use manual or complaint.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Request Body

NameTypeRequiredDescription
reasonenum
Optional
`manual` (default) or `complaint`
notesstring
Optional
Free-form note shown in the audit trail (max 2000 chars)
channelenum
Optional
Which medium to suppress: `all` (default), `voice`, `sms` or `email`. Omit it to suppress everything, which is the behaviour that predates channel-scoped DNC.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "manual",
    "notes": "Customer emailed support asking to be removed from all outreach."
  }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "dnc_record_id": "a1b2c3d4-...",
    "reason": "manual"
  }
}
DELETE

Revoke DNC

https://app.aivi.io/ignite/api/v1/contacts/:id/dnc
contacts:dnc_manage

Mark all active DNC records on the contact as revoked. The audit rows are preserved (revoked_at, revoked_by_user_id, revoked_reason) for TCPA defense — they are never hard-deleted. Requires a separate scope (contacts:dnc_manage) because litigator entries should not be casually un-flagged.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Request Body

NameTypeRequiredDescription
reasonstring
Required
Why the DNC is being lifted. Stored on every revoked row.

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "False positive — contact contacted us asking to re-engage." }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "revoked_count": 2
  }
}
POST

Suppress one phone or email

https://app.aivi.io/ignite/api/v1/contacts/:id/phones/:point_id/dnc
contacts:write

Stop contacting a SINGLE number or address — optionally only over one medium — while the contact's other endpoints stay reachable — the 'stop calling my work line, my mobile is fine' case. Replace `phones` with `emails` in the path to suppress an email address. Unlike contact-level DNC this does NOT cancel workflow enrollments and does NOT revoke consent records, because suppressing one channel is a narrower act than suppressing a person. The suppressed entry reports is_dnc: true and lists the suppressed media in dnc_channels in the contact's phones[] / emails[] array, and outreach to that channel returns 403 DNC_BLOCKED. Idempotent per (endpoint, reason, channel).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID
point_iduuid
Required
The phones[].id / emails[].id of the channel to suppress

Request Body

NameTypeRequiredDescription
reasonstring
Optional
'manual' (default) or 'complaint'
notesstring
Optional
Free-text context stored on the audit row
channelenum
Optional
Which medium to suppress on this endpoint: `all` (default), `voice` or `sms` for a number, `email` for an address. Use it for 'stop texting this number, calls are fine'.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/phones/770e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "manual", "notes": "Asked us to stop calling the work line." }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "point_id": "770e8400-e29b-41d4-a716-446655440000",
    "dnc_record_id": "a1b2c3d4-...",
    "reason": "manual"
  }
}
DELETE

Un-suppress one phone or email

https://app.aivi.io/ignite/api/v1/contacts/:id/phones/:point_id/dnc
contacts:dnc_manage

Revoke the active DNC records for a single channel. Never touches a contact-level DNC: if the contact as a whole is suppressed it stays suppressed, and this channel remains unreachable until that is revoked separately. Audit rows are preserved, never hard-deleted. Requires contacts:dnc_manage.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID
point_iduuid
Required
The phones[].id / emails[].id to un-suppress

Request Body

NameTypeRequiredDescription
reasonstring
Required
Why the suppression is being lifted. Stored on every revoked row.
channelenum
Optional
Lift only this medium (`voice`, `sms` or `email`). If the endpoint was suppressed on every channel, lifting one keeps the others suppressed: the all-channel record is revoked and the remaining media are re-recorded with the original reason. Omit it, or send `all`, to lift every channel on this endpoint. `revoked_count` is 0 when nothing on that medium was suppressed.

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/phones/770e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Contact confirmed the work line is fine again." }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "point_id": "770e8400-e29b-41d4-a716-446655440000",
    "revoked_count": 1
  }
}
POST

Suppress one email

https://app.aivi.io/ignite/api/v1/contacts/:id/emails/:point_id/dnc
contacts:write

Stop emailing a SINGLE address while the contact's other endpoints stay reachable. This is the email counterpart of suppressing a phone number: it does NOT cancel workflow enrollments and does NOT revoke consent records. The suppressed entry reports is_dnc: true and lists email in dnc_channels in the contact's emails[] array, and outreach to that address returns 403 DNC_BLOCKED. Idempotent per (endpoint, reason, channel).

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID
point_iduuid
Required
The emails[].id of the address to suppress

Request Body

NameTypeRequiredDescription
reasonstring
Optional
'manual' (default) or 'complaint'
notesstring
Optional
Free-text context stored on the audit row
channelenum
Optional
Which medium to suppress on this address: `all` (default) or `email`.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/emails/770e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "complaint", "notes": "Asked us to stop emailing this address." }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "point_id": "770e8400-e29b-41d4-a716-446655440000",
    "dnc_record_id": "a1b2c3d4-...",
    "reason": "complaint"
  }
}
DELETE

Un-suppress one email

https://app.aivi.io/ignite/api/v1/contacts/:id/emails/:point_id/dnc
contacts:dnc_manage

Revoke the active DNC records for a single email address. Never touches a contact-level DNC: if the contact as a whole is suppressed it stays suppressed, and this address remains unreachable until that is revoked separately. Audit rows are preserved, never hard-deleted. Requires contacts:dnc_manage.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID
point_iduuid
Required
The emails[].id to un-suppress

Request Body

NameTypeRequiredDescription
reasonstring
Required
Why the suppression is being lifted. Stored on every revoked row.
channelenum
Optional
Lift only this medium (`email` or `all`). Omit it to lift every channel on this address.

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/emails/770e8400-e29b-41d4-a716-446655440000/dnc \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Contact confirmed the address is fine again." }'

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "point_id": "770e8400-e29b-41d4-a716-446655440000",
    "revoked_count": 1
  }
}

Insights

AIVI Insights enriches a phone number (and optionally a name, email or address) with third-party data and scores it into a tier. Enrichment is a paidoperation: each successful call is billed against your organization's funds. A request with insufficient funds returns 402 insufficient_funds, and a lookup that finds nothing returns 422 no_enrichment_data.

Use POST /insights for an ad-hoc lookup (nothing is saved), or POST /contacts/:id/enrich to enrich an existing contact. Both require the insights:enrich scope.

The Insights Result

NameTypeRequiredDescription
enriched_fieldsinteger
Optional
Number of fields the enrichment found
apis_with_datastring[]
Optional
Data providers that returned results
tierstring | null
Optional
Lead quality tier
tier_labelstring | null
Optional
Human-readable tier label
tier_actionstring | null
Optional
Suggested next action for the tier
fieldsobject
Optional
The enriched data, keyed by field name
enriched_attimestamp
Optional
When the enrichment ran
contact_iduuid
Optional
The enriched contact. Present on POST /contacts/:id/enrich only.
POST

Query Insights

https://app.aivi.io/ignite/api/v1/insights
insights:enrich

Run a paid enrichment lookup for a phone number without creating a contact. Returns 402 insufficient_funds when the organization cannot cover the charge and 422 no_enrichment_data when the lookup finds nothing.

Request Body

NameTypeRequiredDescription
phonestring
Required
Phone number in E.164 format
first_namestring
Optional
First name (improves match quality)
last_namestring
Optional
Last name
emailstring
Optional
Email address
addressstring
Optional
Street address
citystring
Optional
City
statestring
Optional
State or region
zipstring
Optional
Postal code

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/insights \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+12025551234", "first_name": "Jane", "last_name": "Doe" }'

Response
200

json
{
  "success": true,
  "data": {
    "enriched_fields": 12,
    "apis_with_data": [
      "demographics",
      "property"
    ],
    "tier": "A",
    "tier_label": "High intent",
    "tier_action": "Call within 5 minutes",
    "fields": {
      "age_range": "35-44",
      "homeowner": true
    },
    "enriched_at": "2026-05-04T17:30:00Z"
  }
}
POST

Enrich Contact

https://app.aivi.io/ignite/api/v1/contacts/:id/enrich
insights:enrich

Run paid AIVI Insights enrichment for an existing contact. Returns 402 insufficient_funds when the organization cannot cover the charge, 404 when the contact does not exist, and 422 no_enrichment_data when the lookup finds nothing.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The contact ID

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/enrich \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "contact_id": "550e8400-e29b-41d4-a716-446655440000",
    "enriched_fields": 12,
    "apis_with_data": [
      "demographics",
      "property"
    ],
    "tier": "A",
    "tier_label": "High intent",
    "tier_action": "Call within 5 minutes",
    "fields": {
      "age_range": "35-44",
      "homeowner": true
    },
    "enriched_at": "2026-05-04T17:30:00Z"
  }
}

Calls

Calls represent voice interactions with contacts — both inbound and outbound. Each call belongs to an organization and is optionally associated with a contact. Completed calls may include transcripts, AI-generated summaries, and recording URLs.

The list endpoint returns a light payload optimized for pagination. The detail endpoint adds the full transcript, structured conversation intelligence, and a freshly-minted signed recording URL valid for one hour.

The Call Object

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contact_id": "660e8400-e29b-41d4-a716-446655440000",
  "direction": "outbound",
  "status": "completed",
  "outcome": "answered",
  "phone_number": "+12025551234",
  "from_number": "+12027778888",
  "to_number": "+12025551234",
  "initiated_at": "2026-05-04T17:30:00Z",
  "started_at": "2026-05-04T17:30:05Z",
  "ended_at": "2026-05-04T17:34:12Z",
  "duration": 247,
  "call_summary": "Brief check-in. Customer interested in premium plan.",
  "source": "phone",
  "created_at": "2026-05-04T17:30:00Z",
  "updated_at": "2026-05-04T17:34:15Z"
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique identifier (read-only)
contact_iduuid | null
Optional
Associated contact, or null if not linked
directionenum
Optional
inbound or outbound
statusenum
Optional
initiated, ringing, in-progress, completed, busy, failed, no-answer, cancelled
outcomeenum | null
Optional
answered, no-answer, busy, voicemail, failed, canceled, transferred (set after the call completes)
phone_numberstring | null
Optional
Primary phone associated with the call
from_numberstring | null
Optional
Caller number (E.164)
to_numberstring | null
Optional
Recipient number (E.164)
initiated_attimestamp | null
Optional
When the call was initiated
started_attimestamp | null
Optional
When the call connected
ended_attimestamp | null
Optional
When the call ended
durationinteger
Optional
Call length in seconds
call_summarystring | null
Optional
AI-generated plain-text summary (post-call pipeline)
sourceenum
Optional
phone (real telephony) or upload (manual evaluation upload)
transcript_textstring | null
Optional
Full transcript as plain text. Detail endpoint only.
transcript_objectarray | null
Optional
Structured transcript: array of {role, content} objects. Detail endpoint only.
call_intelligenceobject
Optional
AI conversation intelligence: summary, sentiment, category, action_items, topics, key_quotes, quality_metrics. Detail endpoint only.
recording_urlstring | null
Optional
AIVI recording URL (`/ignite/api/v1/recordings/{call_id}`). Requests to it with your API key redirect or stream the audio. Detail endpoint only.
recording_durationinteger | null
Optional
Recording length in seconds. Detail endpoint only.
created_attimestamp
Optional
Creation time (read-only)
updated_attimestamp
Optional
Last update time (read-only)
GET

List Calls

https://app.aivi.io/ignite/api/v1/calls
calls:read

Retrieve a paginated list of calls. Returns the light payload — transcripts, intelligence, and recording URLs are only available via the detail endpoint.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
contact_iduuid
Optional
Filter by associated contact
directionstring
Optional
Filter by direction (inbound, outbound)
statusstring
Optional
Filter by call status
outcomestring
Optional
Filter by outcome (answered, no-answer, busy, voicemail, failed, canceled, transferred)
from_datetimestamp
Optional
Lower bound on created_at (ISO 8601)
to_datetimestamp
Optional
Upper bound on created_at (ISO 8601)
sortstring
Optional
Sort field and direction. Allowed: created_at, started_at, ended_at, duration Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/calls?contact_id=660e8400-e29b-41d4-a716-446655440000&page_size=10" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "contact_id": "660e8400-e29b-41d4-a716-446655440000",
      "direction": "outbound",
      "status": "completed",
      "outcome": "answered",
      "phone_number": "+12025551234",
      "from_number": "+12027778888",
      "to_number": "+12025551234",
      "initiated_at": "2026-05-04T17:30:00Z",
      "started_at": "2026-05-04T17:30:05Z",
      "ended_at": "2026-05-04T17:34:12Z",
      "duration": 247,
      "call_summary": "Brief check-in. Customer interested in premium plan.",
      "source": "phone",
      "created_at": "2026-05-04T17:30:00Z",
      "updated_at": "2026-05-04T17:34:15Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 10,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Call

https://app.aivi.io/ignite/api/v1/calls/:id
calls:read

Retrieve a single call by its unique ID. Includes the full transcript, structured conversation intelligence, and an AIVI-branded recording URL. The recording_url 302-redirects to the underlying audio file when followed; ensure your HTTP client follows redirects (most do by default). Each follow mints a fresh download URL — no need to track expirations.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The call ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/calls/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "direction": "outbound",
    "status": "completed",
    "outcome": "answered",
    "phone_number": "+12025551234",
    "from_number": "+12027778888",
    "to_number": "+12025551234",
    "initiated_at": "2026-05-04T17:30:00Z",
    "started_at": "2026-05-04T17:30:05Z",
    "ended_at": "2026-05-04T17:34:12Z",
    "duration": 247,
    "call_summary": "Brief check-in. Customer interested in premium plan.",
    "source": "phone",
    "transcript_text": "Agent: Hi Jane, this is Alex from AIVI...\nJane: Hi Alex, yes I had a quick question...",
    "transcript_object": [
      {
        "role": "agent",
        "content": "Hi Jane, this is Alex from AIVI..."
      },
      {
        "role": "user",
        "content": "Hi Alex, yes I had a quick question..."
      }
    ],
    "call_intelligence": {
      "summary": "Brief check-in. Customer interested in premium plan.",
      "sentiment": "positive",
      "category": "sales_followup",
      "topics": [
        "premium_plan",
        "pricing"
      ],
      "action_items": [
        "Send pricing PDF"
      ]
    },
    "recording_url": "https://app.aivi.io/ignite/api/v1/recordings/<call_id>",
    "recording_duration": 247,
    "created_at": "2026-05-04T17:30:00Z",
    "updated_at": "2026-05-04T17:34:15Z"
  }
}
POST

Initiate Call

https://app.aivi.io/ignite/api/v1/calls
calls:initiate

Dispatch an outbound AI agent call. The endpoint reserves call funds (returns 402 insufficient_funds when low), inserts the call record, and hands off to the org's voice provider. Returns the call_id immediately — poll GET /calls/:id (or listen for webhooks) for status updates, transcript, and recording. Requires an AI-enabled phone number (assign one in Settings → Phone Numbers if you haven't yet). The agent must be active (409 agent_not_active otherwise). Returns 409 agent_not_ready while the voice provider assistant has not been provisioned yet (usually seconds after activating a new or duplicated agent); retry shortly, and check sync_status on GET /ai_agents/:id.

Request Body

NameTypeRequiredDescription
agent_iduuid
Required
AI agent that will run the call. Must belong to your organization.
to_numberstring (E.164)
Required
Destination phone number, e.g. +14155551234.
from_phone_number_iduuid
Required
ID from /phone_numbers. Must be active and AI-enabled.
contact_iduuid
Optional
Optional contact this call is associated with.
system_prompt_overridestring
Optional
Replace the agent's system prompt for this call only.
greeting_overridestring
Optional
Replace the agent's greeting for this call only.
metadataobject
Optional
Free-form metadata merged into the call record.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/calls \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "to_number": "+12025551234",
    "from_phone_number_id": "880e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000"
  }'

Response
200

json
{
  "success": true,
  "data": {
    "call_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "initiated",
    "direction": "outbound",
    "from_number": "+12027778888",
    "to_number": "+12025551234",
    "initiated_at": "2026-05-06T19:30:00Z"
  }
}

Call Intelligence

Call intelligence is the structured analysis the postcall pipeline produces from a call's transcript: an AI-generated summary plus a structured object containing sentiment, category, action items, topics, key quotes, and quality metrics. The exact keys evolve as the pipeline grows.

This endpoint is a focused projection — partners that just want "what happened?" without pulling the full call payload (transcript, metadata, recording URL) hit /call_intelligence/{call_id}. The same data is also available on the call detail endpoint GET /calls/:id.

Returns an empty intelligence object when the call has not been analyzed yet (e.g. it is still in progress or the postcall pipeline has not run).

GET

Get Call Intelligence

https://app.aivi.io/ignite/api/v1/call_intelligence/:call_id
calls:read

Retrieve the postcall intelligence for a specific call: AI-generated summary plus the structured analysis object. Uses the calls:read scope — the same scope already required to fetch the parent call.

Path Parameters

NameTypeRequiredDescription
call_iduuid
Required
The call ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/call_intelligence/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "call_id": "550e8400-e29b-41d4-a716-446655440000",
    "call_summary": "Agent walked Jane through the premium plan. Jane asked about onboarding timelines and committed to a follow-up next Tuesday.",
    "intelligence": {
      "summary": "Discovery call covering pricing, onboarding timeline, and decision criteria.",
      "sentiment": "positive",
      "category": "sales_discovery",
      "action_items": [
        "Send pricing PDF by EOD",
        "Schedule follow-up for Tuesday at 2pm ET"
      ],
      "topics_discussed": [
        "pricing",
        "onboarding",
        "integration"
      ],
      "key_quotes": [
        {
          "speaker": "customer",
          "text": "I love the integration story — that was my biggest concern."
        }
      ],
      "quality_metrics": {
        "clarity": 0.92,
        "empathy": 0.78
      }
    },
    "ended_at": "2026-05-04T17:34:12Z",
    "created_at": "2026-05-04T17:30:00Z"
  }
}

Recordings

Call recordings are delivered through AIVI, never through the underlying telephony vendor. The recording_url on a call points at this endpoint. Recordings stored by AIVI respond with a 302 redirect to a fresh one-hour download URL; vendor-hosted recordings are streamed back with the audio content type. Make sure your HTTP client follows redirects. Requires the calls:read scope.

GET

Get Recording

https://app.aivi.io/ignite/api/v1/recordings/:call_id
calls:read

Download the recording for a call. Responds with a 302 redirect to the audio file, or streams the audio directly when it is hosted by a telephony vendor. Returns 404 not_found when the call does not exist and 404 no_recording when the call has no recording.

Path Parameters

NameTypeRequiredDescription
call_iduuid
Required
The call ID

Example Request

bash
curl -L https://app.aivi.io/ignite/api/v1/recordings/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -o call-recording.mp3

Response
302

json
HTTP/1.1 302 Found
Location: https://<signed-download-url>

# Vendor-hosted recordings return 200 with the audio stream instead.

Messages

Messages represent SMS, MMS, and WhatsApp communications. Each message belongs to an organization and a conversation, and is optionally associated with a contact. Email is a separate resource and is not exposed via this endpoint.

The Message Object

json
{
  "id": "770e8400-e29b-41d4-a716-446655440000",
  "contact_id": "660e8400-e29b-41d4-a716-446655440000",
  "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
  "direction": "inbound",
  "channel": "sms",
  "status": "received",
  "from_number": "+12025551234",
  "to_number": "+12027778888",
  "body": "Yes, I'm interested!",
  "media_urls": null,
  "is_read": false,
  "sent_at": "2026-05-04T17:35:00Z",
  "delivered_at": null,
  "read_at": null,
  "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "created_at": "2026-05-04T17:35:00Z",
  "updated_at": "2026-05-04T17:35:00Z"
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique identifier (read-only)
contact_iduuid | null
Optional
Associated contact, or null if not linked
conversation_iduuid
Optional
Conversation thread the message belongs to
directionenum
Optional
inbound or outbound
channelenum
Optional
sms, mms, or whatsapp
statusenum
Optional
accepted, queued, sent, delivered, undelivered, failed, received
from_numberstring
Optional
Sender number (E.164)
to_numberstring
Optional
Recipient number (E.164)
bodystring
Optional
Message body (plain text)
media_urlsstring[] | null
Optional
MMS attachments. Any URL you supplied is passed through unchanged; internally-stored attachments come back as short-lived signed URLs (expire in 1 hour) — re-fetch the message via GET rather than caching those. null for sms/whatsapp text-only messages.
is_readboolean
Optional
Whether the message has been read in the dashboard
sent_attimestamp | null
Optional
When the message was sent
delivered_attimestamp | null
Optional
When delivery was confirmed by the carrier
read_attimestamp | null
Optional
When the message was first read
message_sidstring | null
Optional
Provider-side message ID (e.g. Twilio SID)
created_attimestamp
Optional
Creation time (read-only)
updated_attimestamp
Optional
Last update time (read-only)
GET

List Messages

https://app.aivi.io/ignite/api/v1/messages
messages:read

Retrieve a paginated list of SMS, MMS, and WhatsApp messages. Filter by contact, conversation, channel, direction, or date range.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
contact_iduuid
Optional
Filter by associated contact
conversation_iduuid
Optional
Filter by conversation thread
channelstring
Optional
Filter by channel (sms, mms, whatsapp)
directionstring
Optional
Filter by direction (inbound, outbound)
from_datetimestamp
Optional
Lower bound on created_at (ISO 8601)
to_datetimestamp
Optional
Upper bound on created_at (ISO 8601)
sortstring
Optional
Sort field and direction. Allowed: created_at, sent_at, delivered_at Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/messages?contact_id=660e8400-e29b-41d4-a716-446655440000&channel=sms" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "contact_id": "660e8400-e29b-41d4-a716-446655440000",
      "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
      "direction": "inbound",
      "channel": "sms",
      "status": "received",
      "from_number": "+12025551234",
      "to_number": "+12027778888",
      "body": "Yes, I'm interested!",
      "media_urls": null,
      "is_read": false,
      "sent_at": "2026-05-04T17:35:00Z",
      "delivered_at": null,
      "read_at": null,
      "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "created_at": "2026-05-04T17:35:00Z",
      "updated_at": "2026-05-04T17:35:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Message

https://app.aivi.io/ignite/api/v1/messages/:id
messages:read

Retrieve a single message by its unique ID.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The message ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/messages/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
    "direction": "inbound",
    "channel": "sms",
    "status": "received",
    "from_number": "+12025551234",
    "to_number": "+12027778888",
    "body": "Yes, I'm interested!",
    "media_urls": null,
    "is_read": false,
    "sent_at": "2026-05-04T17:35:00Z",
    "delivered_at": null,
    "read_at": null,
    "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "created_at": "2026-05-04T17:35:00Z",
    "updated_at": "2026-05-04T17:35:00Z"
  }
}
POST

Send Message

https://app.aivi.io/ignite/api/v1/messages
messages:write

Send an SMS or MMS to a contact. The contact must have a phone number on file. The conversation between your organization and the contact is created automatically if it doesn't exist. Each message is billed against your organization's funds — insufficient funds return 402. US sender numbers require A2P 10DLC registration; missing registration returns 422 compliance_required. Include media_urls (publicly reachable attachment URLs, up to 10) to send an MMS instead of an SMS — it's billed flat per-message rather than per-segment.

Request Body

NameTypeRequiredDescription
contact_iduuid
Required
The recipient contact (must have a phone number)
from_numberstring
Required
Sender phone number in E.164 format. Must be one of your organization's verified Twilio numbers.
bodystring
Required
Message text (1-1600 characters)
media_urlsstring[]
Optional
Up to 10 publicly reachable attachment URLs (images, video, PDF). Presence of at least one item sends the message as MMS instead of SMS.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/messages \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "from_number": "+12027778888",
    "body": "Hi Jane, just checking in!",
    "media_urls": ["https://example.com/photo.jpg"]
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
    "direction": "outbound",
    "channel": "mms",
    "status": "queued",
    "from_number": "+12027778888",
    "to_number": "+12025551234",
    "body": "Hi Jane, just checking in!",
    "media_urls": [
      "https://xxxxxxxx.supabase.co/storage/v1/object/sign/mms-media/..."
    ],
    "is_read": true,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "created_at": "2026-05-04T17:35:00Z",
    "updated_at": "2026-05-04T17:35:00Z"
  }
}

Emails

Emails represent inbound and outbound email communications. Each email belongs to an organization and is optionally associated with a contact. Both plain-text and HTML bodies are stored, along with delivery, open, and click tracking from the sending provider.

The list endpoint returns a light payload — message bodies, cc, bcc, and error details are only available via the detail endpoint, since HTML email bodies can be large.

The Email Object

json
{
  "id": "bb0e8400-e29b-41d4-a716-446655440000",
  "contact_id": "660e8400-e29b-41d4-a716-446655440000",
  "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
  "direction": "outbound",
  "status": "delivered",
  "from_email": "support@example.com",
  "from_name": "Acme Support",
  "to_email": "jane@example.com",
  "reply_to": null,
  "subject": "Welcome to Acme",
  "sent_at": "2026-05-04T17:30:00Z",
  "delivered_at": "2026-05-04T17:30:05Z",
  "opened_at": "2026-05-04T18:42:00Z",
  "clicked_at": null,
  "open_count": 1,
  "click_count": 0,
  "is_read": true,
  "sendgrid_message_id": "sg-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "created_at": "2026-05-04T17:30:00Z",
  "updated_at": "2026-05-04T18:42:00Z"
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique identifier (read-only)
contact_iduuid | null
Optional
Associated contact, or null if not linked
conversation_iduuid | null
Optional
Conversation thread the email belongs to
directionenum
Optional
inbound or outbound
statusenum
Optional
queued, sent, delivered, opened, clicked, bounced, dropped, failed
from_emailstring
Optional
Sender email address
from_namestring | null
Optional
Sender display name
to_emailstring
Optional
Recipient email address
reply_tostring | null
Optional
Reply-To header (if different from from_email)
subjectstring
Optional
Email subject
sent_attimestamp | null
Optional
When the email was handed off to the provider
delivered_attimestamp | null
Optional
When the recipient mail server accepted delivery
opened_attimestamp | null
Optional
When the email was first opened
clicked_attimestamp | null
Optional
When a link in the email was first clicked
open_countinteger
Optional
Total number of opens recorded
click_countinteger
Optional
Total number of link clicks recorded
is_readboolean
Optional
Whether the email has been read in the dashboard
sendgrid_message_idstring | null
Optional
Provider-side message ID
bodystring | null
Optional
Plain-text body. Detail endpoint only.
html_bodystring | null
Optional
HTML body. Detail endpoint only.
ccstring[] | null
Optional
CC recipients. Detail endpoint only.
bccstring[] | null
Optional
BCC recipients. Detail endpoint only.
bounced_attimestamp | null
Optional
When the email bounced. Detail endpoint only.
error_messagestring | null
Optional
Provider error if delivery failed. Detail endpoint only.
error_codestring | null
Optional
Provider error code. Detail endpoint only.
created_attimestamp
Optional
Creation time (read-only)
updated_attimestamp
Optional
Last update time (read-only)
GET

List Emails

https://app.aivi.io/ignite/api/v1/emails
emails:read

Retrieve a paginated list of emails. Filter by contact, conversation, direction, status, or date range. Returns the light payload — bodies and full delivery error details are only available via the detail endpoint.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
contact_iduuid
Optional
Filter by associated contact
conversation_iduuid
Optional
Filter by conversation thread
directionstring
Optional
Filter by direction (inbound, outbound)
statusstring
Optional
Filter by status (queued, sent, delivered, opened, clicked, bounced, dropped, failed)
from_datetimestamp
Optional
Lower bound on created_at (ISO 8601)
to_datetimestamp
Optional
Upper bound on created_at (ISO 8601)
sortstring
Optional
Sort field and direction. Allowed: created_at, sent_at, delivered_at Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/emails?contact_id=660e8400-e29b-41d4-a716-446655440000&status=delivered" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "bb0e8400-e29b-41d4-a716-446655440000",
      "contact_id": "660e8400-e29b-41d4-a716-446655440000",
      "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
      "direction": "outbound",
      "status": "delivered",
      "from_email": "support@example.com",
      "from_name": "Acme Support",
      "to_email": "jane@example.com",
      "reply_to": null,
      "subject": "Welcome to Acme",
      "sent_at": "2026-05-04T17:30:00Z",
      "delivered_at": "2026-05-04T17:30:05Z",
      "opened_at": "2026-05-04T18:42:00Z",
      "clicked_at": null,
      "open_count": 1,
      "click_count": 0,
      "is_read": true,
      "sendgrid_message_id": "sg-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "created_at": "2026-05-04T17:30:00Z",
      "updated_at": "2026-05-04T18:42:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Email

https://app.aivi.io/ignite/api/v1/emails/:id
emails:read

Retrieve a single email by its unique ID. Includes the plain-text body, HTML body, CC/BCC recipients, and any delivery error details.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The email ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/emails/bb0e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "bb0e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
    "direction": "outbound",
    "status": "delivered",
    "from_email": "support@example.com",
    "from_name": "Acme Support",
    "to_email": "jane@example.com",
    "reply_to": null,
    "subject": "Welcome to Acme",
    "body": "Hi Jane,\n\nWelcome to Acme! Click here to get started: https://...",
    "html_body": "<p>Hi Jane,</p><p>Welcome to Acme! <a href=\"https://...\">Click here</a> to get started.</p>",
    "cc": null,
    "bcc": null,
    "sent_at": "2026-05-04T17:30:00Z",
    "delivered_at": "2026-05-04T17:30:05Z",
    "opened_at": "2026-05-04T18:42:00Z",
    "clicked_at": null,
    "bounced_at": null,
    "open_count": 1,
    "click_count": 0,
    "is_read": true,
    "error_message": null,
    "error_code": null,
    "sendgrid_message_id": "sg-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "created_at": "2026-05-04T17:30:00Z",
    "updated_at": "2026-05-04T18:42:00Z"
  }
}
POST

Send Email

https://app.aivi.io/ignite/api/v1/emails
emails:write

Send an email to a contact via your organization's verified sender. The contact must have an email address on file. The conversation between your organization and the contact is created automatically. If from_sender_id is omitted, your organization's default sender is used. Returns 422 sender_not_configured if no default sender is set or the chosen sender hasn't completed domain verification.

Request Body

NameTypeRequiredDescription
contact_iduuid
Required
The recipient contact (must have an email address)
subjectstring
Required
Email subject (1-998 characters)
bodystring
Required
Plain-text body. If html_body is omitted, this is converted to safe HTML automatically.
html_bodystring
Optional
HTML body. Overrides the auto-conversion of body.
ccstring[]
Optional
CC recipients (array of email addresses, max 50)
bccstring[]
Optional
BCC recipients (array of email addresses, max 50)
reply_tostring
Optional
Reply-To header. Overrides the sender's default reply_to.
from_sender_iduuid
Optional
A specific sender from email_sender_configs. Omit to use your org's default sender.

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/emails \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "subject": "Welcome to Acme",
    "body": "Hi Jane,\n\nWelcome aboard! Reply if you have any questions.\n"
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "bb0e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "conversation_id": "880e8400-e29b-41d4-a716-446655440000",
    "direction": "outbound",
    "status": "sent",
    "from_email": "support@acme.com",
    "from_name": "Acme Support",
    "to_email": "jane@example.com",
    "reply_to": null,
    "subject": "Welcome to Acme",
    "body": "Hi Jane,\n\nWelcome aboard! Reply if you have any questions.\n",
    "html_body": "<p>Hi Jane,</p><p>Welcome aboard! Reply if you have any questions.</p>",
    "cc": null,
    "bcc": null,
    "sent_at": "2026-05-04T17:30:00Z",
    "delivered_at": null,
    "opened_at": null,
    "clicked_at": null,
    "bounced_at": null,
    "open_count": 0,
    "click_count": 0,
    "is_read": true,
    "error_message": null,
    "error_code": null,
    "sendgrid_message_id": "sg-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "created_at": "2026-05-04T17:30:00Z",
    "updated_at": "2026-05-04T17:30:00Z"
  }
}

Workflows

Workflows are graph-based automations that act on contacts — sending messages, making calls, branching on conditions, waiting for events. Each workflow belongs to an organization and has a trigger type that determines what kicks it off.

The API exposes the active workflows in your org and lets you enroll contacts into them programmatically. Enrollment is asynchronous — calling the enroll endpoint enqueues a job; the workflow worker creates the enrollment row and starts execution. Poll GET /workflow_enrollments to observe lifecycle.

The Workflow Object

json
{
  "id": "990e8400-e29b-41d4-a716-446655440000",
  "name": "New Lead Welcome Sequence",
  "description": "Send a welcome SMS, wait 1 day, then call.",
  "status": "active",
  "is_active": true,
  "trigger_type": "manual",
  "is_ai_autopilot": false,
  "created_at": "2026-04-12T10:00:00Z",
  "updated_at": "2026-04-15T14:30:00Z"
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique identifier (read-only)
namestring
Optional
Display name
descriptionstring | null
Optional
Optional description
statusenum
Optional
active, completed, failed, draft, archived
is_activeboolean
Optional
Whether the workflow is enabled. Both status=active AND is_active=true are required for enrollment.
trigger_typestring
Optional
What kicks the workflow off: manual, contact_created, contact_updated, webhook, date_time, etc.
is_ai_autopilotboolean
Optional
Whether this is an AI-autopilot workflow (ML-driven node selection)
created_attimestamp
Optional
Creation time (read-only)
updated_attimestamp
Optional
Last update time (read-only)
GET

List Workflows

https://app.aivi.io/ignite/api/v1/workflows
workflows:read

List workflows in your organization. By default returns only enrollable workflows (status=active AND is_active=true). Pass include_inactive=true to see drafts and archived.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
statusstring
Optional
Filter by workflow status (active, completed, failed, draft, archived)
include_inactiveboolean
Optional
Include drafts, archived, and other non-enrollable workflows Default: false
sortstring
Optional
Sort field and direction. Allowed: created_at, updated_at, name Default: created_at:desc

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/workflows \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "name": "New Lead Welcome Sequence",
      "description": "Send a welcome SMS, wait 1 day, then call.",
      "status": "active",
      "is_active": true,
      "trigger_type": "manual",
      "is_ai_autopilot": false,
      "created_at": "2026-04-12T10:00:00Z",
      "updated_at": "2026-04-15T14:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id
workflows:read

Retrieve a single workflow with its full node + edge graph and trigger configuration. Use this before authoring an update so you can replay the existing graph.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to retrieve

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "name": "New Lead Welcome Sequence",
    "description": "Send a welcome SMS, wait 1 day, then call.",
    "status": "active",
    "is_active": true,
    "is_ai_autopilot": false,
    "trigger_type": "contact_created",
    "trigger_config": {},
    "timezone": "America/New_York",
    "webhook_path": null,
    "created_at": "2026-04-12T10:00:00Z",
    "updated_at": "2026-04-15T14:30:00Z",
    "nodes": [
      {
        "node_key": "t1",
        "node_type": "trigger",
        "action_type": null,
        "label": "Contact Created",
        "config": {},
        "position_x": 250,
        "position_y": 0
      },
      {
        "node_key": "sms1",
        "node_type": "action",
        "action_type": "send_sms",
        "label": "Welcome SMS",
        "config": {
          "message": "Hi {{contact.firstName}}!",
          "messageType": "static",
          "toNumberSource": "phone"
        },
        "position_x": 250,
        "position_y": 150
      }
    ],
    "edges": [
      {
        "edge_key": "e1",
        "source_node_key": "t1",
        "target_node_key": "sms1",
        "source_handle": null,
        "target_handle": null,
        "label": "default",
        "config": {}
      }
    ]
  }
}
POST

Create Workflow

https://app.aivi.io/ignite/api/v1/workflows
workflows:write

Create a new workflow with its full graph in one atomic call. Always lands as status='draft' and is_active=false — call POST /workflows/:id/activate to make it enrollable. Strict validation runs at ingest: exactly one trigger node, paired for_each_contact ↔ loop_end, condition nodes have true+false outgoing edges, action configs have their required fields. Failed validation returns 422 with a validation_errors[] array. Position fields are optional — server runs auto-layout when omitted.

Request Body

NameTypeRequiredDescription
namestring
Required
Workflow name (1–255 chars)
descriptionstring
Optional
Optional description (max 2000)
triggerobject
Required
{type, config?} — type from manual | contact_created | contact_updated | webhook | date_time | meta_lead | qa_evaluation.completed | moment_detected
timezonestring
Optional
IANA timezone (e.g. America/New_York). Defaults to org timezone when omitted.
nodesarray
Required
Node objects with {node_key, node_type, action_type?, label?, config?, position_x?, position_y?}
edgesarray
Required
Edge objects with {edge_key, source_node_key, target_node_key, source_handle?, target_handle?, label?, config?}

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/workflows \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome Sequence",
    "trigger": {"type": "contact_created", "config": {}},
    "nodes": [
      {"node_key": "t1", "node_type": "trigger", "label": "Contact Created"},
      {"node_key": "sms1", "node_type": "action", "action_type": "send_sms",
       "label": "Welcome SMS",
       "config": {"message": "Hi {{contact.firstName}}!", "messageType": "static",
                  "toNumberSource": "phone"}}
    ],
    "edges": [
      {"edge_key": "e1", "source_node_key": "t1", "target_node_key": "sms1", "label": "default"}
    ]
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440111",
    "name": "Welcome Sequence",
    "description": null,
    "status": "draft",
    "is_active": false,
    "trigger_type": "contact_created",
    "trigger_config": {},
    "timezone": null,
    "nodes": [
      {
        "node_key": "t1",
        "node_type": "trigger",
        "action_type": null,
        "label": "Contact Created",
        "config": {},
        "position_x": 250,
        "position_y": 0
      },
      {
        "node_key": "sms1",
        "node_type": "action",
        "action_type": "send_sms",
        "label": "Welcome SMS",
        "config": {
          "message": "Hi {{contact.firstName}}!",
          "messageType": "static",
          "toNumberSource": "phone"
        },
        "position_x": 250,
        "position_y": 150
      }
    ],
    "edges": [
      {
        "edge_key": "e1",
        "source_node_key": "t1",
        "target_node_key": "sms1",
        "source_handle": null,
        "target_handle": null,
        "label": "default",
        "config": {}
      }
    ]
  }
}
PUT

Update Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id
workflows:write

Atomic full-replace of metadata + graph. Send the entire desired workflow — the server replaces nodes and edges in a single transaction. Validation rules and 422 error shape match Create Workflow: an invalid graph is rejected with 422 and nothing is saved. An active workflow stays active after an update, and no version history is kept. Activating a workflow re-runs validation.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to update

Request Body

NameTypeRequiredDescription
namestring
Required
Workflow name
triggerobject
Required
{type, config?}
nodesarray
Required
Full replacement node list
edgesarray
Required
Full replacement edge list
descriptionstring
Optional
Optional description
timezonestring
Optional
IANA timezone

Example Request

bash
curl -X PUT https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome Sequence (v2)",
    "trigger": {"type": "contact_created", "config": {}},
    "nodes": [...],
    "edges": [...]
  }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "name": "Welcome Sequence (v2)",
    "status": "active",
    "is_active": true
  }
}
DELETE

Delete Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id
workflows:delete

Hard-delete a workflow. By default the request is rejected with 409 if non-terminal enrollments exist (status not in completed/failed/cancelled). Pass ?force=true to override — the cascade removes the workflow, its nodes, edges, and enrollments together.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to delete

Query Parameters

NameTypeRequiredDescription
forceboolean
Optional
Set true to delete despite active enrollments (cascades to enrollment history) Default: false

Example Request

bash
curl -X DELETE "https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000?force=true" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}
POST

Activate Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id/activate
workflows:write

Re-validate the persisted graph and, if valid, set status='active' and is_active=true so the workflow becomes enrollable. Validation is rerun against the saved graph (in case it was edited via the dashboard between authoring and activation). Returns 422 with validation_errors[] when the graph is malformed.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to activate

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000/activate \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "status": "active",
    "is_active": true
  }
}
POST

Deactivate Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id/deactivate
workflows:write

Flip the workflow back to status='draft' and is_active=false. The workflow stops accepting new enrollments. In-flight enrollments are NOT cancelled — they continue executing on the worker.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to deactivate

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000/deactivate \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "status": "draft",
    "is_active": false
  }
}
POST

Duplicate Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id/duplicate
workflows:write

Clone a workflow into a new draft. The clone gets a fresh ID, a fresh webhook_token (when applicable), status='draft', and is_active=false. Optional body {name} overrides the duplicate's name; default is '<original> (Copy)'.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to duplicate

Request Body

NameTypeRequiredDescription
namestring
Optional
Override the duplicate workflow name

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000/duplicate \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Welcome Sequence — Sandbox"}'

Response
201

json
{
  "success": true,
  "data": {
    "id": "bb0e8400-e29b-41d4-a716-446655440222",
    "name": "Welcome Sequence — Sandbox",
    "status": "draft",
    "is_active": false
  }
}
POST

Enroll Contact in Workflow

https://app.aivi.io/ignite/api/v1/workflows/:id/enroll
workflows:enroll

Enroll a contact into a specific workflow. The workflow must be enrollable (status=active AND is_active=true). Returns 202 Accepted — enrollment is asynchronous; the workflow worker creates the enrollment row. Poll GET /workflow_enrollments?contact_id=X&workflow_id=Y to observe its lifecycle. For premium workflows, an account funds check runs first; insufficient funds return 402.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The workflow ID to enroll into

Request Body

NameTypeRequiredDescription
contact_iduuid
Required
The contact to enroll

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/workflows/990e8400-e29b-41d4-a716-446655440000/enroll \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{"contact_id": "660e8400-e29b-41d4-a716-446655440000"}'

Response
202

json
{
  "success": true,
  "data": {
    "workflow_id": "990e8400-e29b-41d4-a716-446655440000",
    "contact_id": "660e8400-e29b-41d4-a716-446655440000",
    "status": "enqueued",
    "message": "Enrollment enqueued. Use GET /workflow_enrollments?contact_id=660e8400-e29b-41d4-a716-446655440000&workflow_id=990e8400-e29b-41d4-a716-446655440000 to observe its lifecycle."
  }
}

Workflow Enrollments

An enrollment represents a single execution of a workflow for a single contact. Enrollments progress through statuses (active → paused / completed / failed / cancelled) as the workflow worker advances through nodes.

The Enrollment Object

json
{
  "id": "aa0e8400-e29b-41d4-a716-446655440000",
  "workflow_id": "990e8400-e29b-41d4-a716-446655440000",
  "contact_id": "660e8400-e29b-41d4-a716-446655440000",
  "status": "active",
  "enrolled_at": "2026-05-04T17:30:00Z",
  "started_at": "2026-05-04T17:30:01Z",
  "completed_at": null,
  "paused_at": null,
  "paused_at_node": null,
  "current_node_key": "wait-1-day",
  "current_step": 2,
  "total_steps": 5,
  "last_executed_at": "2026-05-04T17:30:05Z",
  "error_message": null
}

Fields

NameTypeRequiredDescription
iduuid
Optional
Unique enrollment ID (read-only)
workflow_iduuid
Optional
The workflow this enrollment is executing
contact_iduuid | null
Optional
The contact being enrolled (null for webhook-triggered runs)
statusenum
Optional
active, completed, failed, paused, looping, skipped, cancelled, paused_insufficient_funds
enrolled_attimestamp
Optional
When the enrollment was created
started_attimestamp
Optional
When execution started
completed_attimestamp | null
Optional
When the enrollment finished (success or failure)
paused_attimestamp | null
Optional
When the enrollment was paused (e.g. waiting for an event)
paused_at_nodestring | null
Optional
Node key where execution paused
current_node_keystring | null
Optional
Currently active workflow node
current_stepinteger
Optional
Steps completed so far
total_stepsinteger
Optional
Total steps in the workflow graph
last_executed_attimestamp | null
Optional
When the last node ran
error_messagestring | null
Optional
Error message if status is failed
GET

List Enrollments

https://app.aivi.io/ignite/api/v1/workflow_enrollments
workflows:read

List workflow enrollments in your organization. Filter by contact, workflow, status, or date range.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
contact_iduuid
Optional
Filter by enrolled contact
workflow_iduuid
Optional
Filter by workflow
statusstring
Optional
Filter by enrollment status
from_datetimestamp
Optional
Lower bound on enrolled_at (ISO 8601)
to_datetimestamp
Optional
Upper bound on enrolled_at (ISO 8601)
sortstring
Optional
Sort field and direction. Allowed: enrolled_at, started_at, completed_at, last_executed_at Default: enrolled_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/workflow_enrollments?contact_id=660e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "aa0e8400-e29b-41d4-a716-446655440000",
      "workflow_id": "990e8400-e29b-41d4-a716-446655440000",
      "contact_id": "660e8400-e29b-41d4-a716-446655440000",
      "status": "active",
      "enrolled_at": "2026-05-04T17:30:00Z",
      "started_at": "2026-05-04T17:30:01Z",
      "completed_at": null,
      "paused_at": null,
      "paused_at_node": null,
      "current_node_key": "wait-1-day",
      "current_step": 2,
      "total_steps": 5,
      "last_executed_at": "2026-05-04T17:30:05Z",
      "error_message": null
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
DELETE

Cancel Enrollment

https://app.aivi.io/ignite/api/v1/workflow_enrollments/:id
workflows:enroll

Cancel and remove an active enrollment. The enrollment row is deleted; any in-flight worker jobs become no-ops on next execution. Use this to stop a workflow from continuing to act on a contact.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The enrollment ID

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/workflow_enrollments/aa0e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "aa0e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}

Rubrics

Rubrics define how calls are scored. Each rubric is a weighted set of criteria; when a call ends and the rubric's auto_evaluate flag is on, an evaluation runs automatically and produces a scorecard.

A rubric's agent_type controls which calls it applies to: ai targets AI-handled calls, live targets calls handled by human agents, and both applies to either.

Editing & deletion remain dashboard-only. Editing criteria on an active rubric changes how historical calls would re-score; the dashboard surfaces the affected-evaluation count as a guardrail before letting a user commit. The API exposes create + read so partner systems can seed rubrics, but modifications happen in /ignite/pulse/scorecards.

Criterion Shape

NameTypeRequiredDescription
idstring
Required
Stable identifier for the criterion (any non-empty string)
namestring
Required
Short label shown in scorecards
descriptionstring
Required
What this criterion measures
weightnumber
Required
Relative weight (0-100). Weights are normalized at scoring time.
max_scorenumber
Required
Maximum points achievable for this criterion (1-100)
evaluation_guidancestring
Required
Instructions for the AI scorer on how to evaluate this criterion
typestring
Required
Either "boolean" (pass/fail) or "scale" (graded 0-max_score)
requiredboolean
Required
Whether this criterion must be addressed in every call
GET

List Rubrics

https://app.aivi.io/ignite/api/v1/rubrics
rubrics:read

Retrieve a paginated list of QA rubrics for your organization. Returns the full rubric including criteria.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
agent_typestring
Optional
Filter by agent type (ai, live, both)
is_activeboolean
Optional
Filter by active flag
auto_evaluateboolean
Optional
Filter by whether the rubric auto-runs on call completion
sortstring
Optional
Sort field and direction. Allowed: created_at, updated_at, name Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/rubrics?is_active=true" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "organization_id": "880e8400-e29b-41d4-a716-446655440000",
      "name": "Discovery Call Quality",
      "description": "Scores a sales discovery call on rapport, qualification, and next-step clarity.",
      "criteria": [
        {
          "id": "rapport",
          "name": "Rapport",
          "description": "Did the agent build rapport with the prospect?",
          "weight": 25,
          "max_score": 5,
          "evaluation_guidance": "Award 5 if the agent personalized the conversation and matched tone; 0 if the call was transactional.",
          "type": "scale",
          "required": true
        }
      ],
      "agent_type": "live",
      "is_active": true,
      "auto_evaluate": true,
      "created_at": "2026-04-15T12:00:00Z",
      "updated_at": "2026-04-15T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Rubric

https://app.aivi.io/ignite/api/v1/rubrics/:id
rubrics:read

Retrieve a single rubric by its unique ID, including the full criterion list.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The rubric ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/rubrics/770e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "name": "Discovery Call Quality",
    "description": "Scores a sales discovery call on rapport, qualification, and next-step clarity.",
    "criteria": [
      {
        "id": "rapport",
        "name": "Rapport",
        "description": "Did the agent build rapport with the prospect?",
        "weight": 25,
        "max_score": 5,
        "evaluation_guidance": "Award 5 if the agent personalized the conversation and matched tone; 0 if the call was transactional.",
        "type": "scale",
        "required": true
      },
      {
        "id": "qualification",
        "name": "Qualification",
        "description": "Did the agent qualify budget, timing, and decision-maker?",
        "weight": 50,
        "max_score": 5,
        "evaluation_guidance": "Award full marks only if all three qualifiers were addressed.",
        "type": "scale",
        "required": true
      },
      {
        "id": "next_step",
        "name": "Next Step",
        "description": "Did the agent secure a clear next step?",
        "weight": 25,
        "max_score": 1,
        "evaluation_guidance": "1 if a calendar invite or follow-up time was confirmed; 0 otherwise.",
        "type": "boolean",
        "required": true
      }
    ],
    "agent_type": "live",
    "is_active": true,
    "auto_evaluate": true,
    "created_at": "2026-04-15T12:00:00Z",
    "updated_at": "2026-04-15T12:00:00Z"
  }
}
POST

Create Rubric

https://app.aivi.io/ignite/api/v1/rubrics
rubrics:write

Create a new QA rubric. Once active and auto_evaluate is enabled, every completed call matching agent_type will be scored against this rubric.

Request Body

NameTypeRequiredDescription
namestring
Required
1-200 characters
descriptionstring
Optional
Optional context shown in dashboards (max 2000 characters)
criteriaarray
Required
Array of criterion objects (1-50). See "Criterion Shape" above.
agent_typestring
Optional
ai, live, or both Default: both
is_activeboolean
Optional
Whether the rubric is enabled Default: true
auto_evaluateboolean
Optional
Whether to score every matching call automatically Default: true

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/rubrics \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Discovery Call Quality",
    "description": "Scores rapport, qualification, and next-step clarity.",
    "agent_type": "live",
    "criteria": [
      {
        "id": "rapport",
        "name": "Rapport",
        "description": "Did the agent build rapport with the prospect?",
        "weight": 25,
        "max_score": 5,
        "evaluation_guidance": "Award 5 if the agent personalized the conversation; 0 if the call was transactional.",
        "type": "scale",
        "required": true
      }
    ]
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "name": "Discovery Call Quality",
    "description": "Scores rapport, qualification, and next-step clarity.",
    "criteria": [
      {
        "id": "rapport",
        "name": "Rapport",
        "description": "Did the agent build rapport with the prospect?",
        "weight": 25,
        "max_score": 5,
        "evaluation_guidance": "Award 5 if the agent personalized the conversation; 0 if the call was transactional.",
        "type": "scale",
        "required": true
      }
    ],
    "agent_type": "live",
    "is_active": true,
    "auto_evaluate": true,
    "created_at": "2026-05-05T18:00:00Z",
    "updated_at": "2026-05-05T18:00:00Z"
  }
}

Scorecards

A scorecard is the result of evaluating a single call against a single rubric. The same call may have multiple scorecards if multiple rubrics matched it. Scorecards are produced automatically by the postcall pipeline when auto_evaluate is on, manually from the Scorecards panel, or against ad-hoc audio uploads via the dashboard.

The result field collapses the percentage_score into one of three buckets: ≥70 is pass, 50-69 is review, <50 is fail.

GET

List Scorecards

https://app.aivi.io/ignite/api/v1/scorecards
scorecards:read

Retrieve a paginated list of scorecards. Filters cover the common partner queries: pull every scorecard for a single call, every scorecard against one rubric, or everything that failed in a date range.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
call_iduuid
Optional
Filter by call
rubric_iduuid
Optional
Filter by rubric
resultstring
Optional
Filter by result (pass, fail, review)
evaluator_typestring
Optional
Filter by evaluator (ai, human, hybrid)
min_percentagenumber
Optional
Inclusive lower bound on percentage_score (0-100)
max_percentagenumber
Optional
Inclusive upper bound on percentage_score (0-100)
from_datetimestamp
Optional
Lower bound on created_at (ISO 8601)
to_datetimestamp
Optional
Upper bound on created_at (ISO 8601)
sortstring
Optional
Sort field and direction. Allowed: created_at, percentage_score Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/scorecards?result=fail&from_date=2026-04-01T00:00:00Z" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "organization_id": "880e8400-e29b-41d4-a716-446655440000",
      "call_id": "550e8400-e29b-41d4-a716-446655440000",
      "rubric_id": "770e8400-e29b-41d4-a716-446655440000",
      "evaluator_type": "ai",
      "overall_score": 3.5,
      "max_possible_score": 11,
      "percentage_score": 31.8,
      "result": "fail",
      "criteria_scores": [
        {
          "criterion_id": "rapport",
          "criterion_name": "Rapport",
          "score": 2,
          "max_score": 5,
          "result": "review",
          "evidence": "Agent jumped straight to qualifying questions without warm-up.",
          "notes": "Consider opening with a 30-second connection moment."
        }
      ],
      "ai_notes": "Clear miss on next-step. Strong technical conversation but no calendar invite scheduled.",
      "reviewed_by": null,
      "reviewed_at": null,
      "created_at": "2026-05-04T18:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Scorecard

https://app.aivi.io/ignite/api/v1/scorecards/:id
scorecards:read

Retrieve a single scorecard by its unique ID, including the per-criterion breakdown and AI notes.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The scorecard ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/scorecards/990e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "990e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "call_id": "550e8400-e29b-41d4-a716-446655440000",
    "rubric_id": "770e8400-e29b-41d4-a716-446655440000",
    "evaluator_type": "ai",
    "overall_score": 3.5,
    "max_possible_score": 11,
    "percentage_score": 31.8,
    "result": "fail",
    "criteria_scores": [
      {
        "criterion_id": "rapport",
        "criterion_name": "Rapport",
        "score": 2,
        "max_score": 5,
        "result": "review",
        "evidence": "Agent jumped straight to qualifying questions without warm-up.",
        "transcript_index": 4,
        "notes": "Consider opening with a 30-second connection moment."
      },
      {
        "criterion_id": "qualification",
        "criterion_name": "Qualification",
        "score": 1.5,
        "max_score": 5,
        "result": "fail",
        "evidence": "Budget was discussed; timing and decision-maker were not.",
        "transcript_index": 12,
        "notes": "Two of the three qualifiers missed."
      },
      {
        "criterion_id": "next_step",
        "criterion_name": "Next Step",
        "score": 0,
        "max_score": 1,
        "result": "fail",
        "evidence": "Call ended with \"I will send something over\" — no calendar invite.",
        "notes": "Always close with a confirmed time."
      }
    ],
    "ai_notes": "Clear miss on next-step. Strong technical conversation but no calendar invite scheduled.",
    "reviewed_by": null,
    "reviewed_at": null,
    "created_at": "2026-05-04T18:00:00Z"
  }
}

Chat Widgets

A chat widget is the embeddable website chat that is backed by one of your AI agents. The server-generated embed_key goes into the snippet on your site. Reads require chat_widgets:read; writes and deletes require chat_widgets:write.

The Chat Widget Object

json
{
  "id": "660e8400-e29b-41d4-a716-446655440000",
  "organization_id": "880e8400-e29b-41d4-a716-446655440000",
  "name": "Website Chat",
  "ai_agent_id": "770e8400-e29b-41d4-a716-446655440000",
  "primary_color": "#6366f1",
  "position": "bottom-right",
  "greeting_message": "Hi! How can we help you today?",
  "placeholder_text": "Type a message...",
  "brand_name": "Acme Corp",
  "brand_logo_url": "https://acme.example/logo.png",
  "form_enabled": true,
  "form_title": "Before we chat, tell us about yourself",
  "form_fields": [],
  "form_submit_label": "Start Chat",
  "allowed_domains": [
    "acme.example"
  ],
  "is_active": true,
  "launcher_style": "bubble",
  "launcher_label": null,
  "subtitle": null,
  "theme": "auto",
  "offset_x": 20,
  "offset_y": 20,
  "auto_open_delay": null,
  "video_avatar_agent_id": null,
  "embed_key": "3f9a1c0b7d2e4a68",
  "created_by": "110e8400-e29b-41d4-a716-446655440000",
  "created_at": "2026-05-01T12:00:00Z",
  "updated_at": "2026-05-04T08:30:00Z"
}
GET

List Chat Widgets

https://app.aivi.io/ignite/api/v1/chat_widgets
chat_widgets:read

Retrieve a paginated list of chat widgets.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
is_activeboolean
Optional
Filter by active state
searchstring
Optional
Case-insensitive name substring
sortstring
Optional
Sort field and direction. Allowed: created_at, updated_at, name Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/chat_widgets?is_active=true" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "organization_id": "880e8400-e29b-41d4-a716-446655440000",
      "name": "Website Chat",
      "ai_agent_id": "770e8400-e29b-41d4-a716-446655440000",
      "is_active": true,
      "embed_key": "3f9a1c0b7d2e4a68",
      "created_at": "2026-05-01T12:00:00Z",
      "updated_at": "2026-05-04T08:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Chat Widget

https://app.aivi.io/ignite/api/v1/chat_widgets/:id
chat_widgets:read

Retrieve a single chat widget by its unique ID.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The chat widget ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/chat_widgets/660e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "name": "Website Chat",
    "ai_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "primary_color": "#6366f1",
    "position": "bottom-right",
    "is_active": true,
    "launcher_style": "bubble",
    "theme": "auto",
    "embed_key": "3f9a1c0b7d2e4a68",
    "created_at": "2026-05-01T12:00:00Z",
    "updated_at": "2026-05-04T08:30:00Z"
  }
}
POST

Create Chat Widget

https://app.aivi.io/ignite/api/v1/chat_widgets
chat_widgets:write

Create a chat widget. Only name is required. The embed_key is generated by the server. ai_agent_id and video_avatar_agent_id must belong to your organization.

Request Body

NameTypeRequiredDescription
namestring
Required
Widget name (max 200 chars)
ai_agent_iduuid | null
Optional
AI agent that answers chats
primary_colorstring | null
Optional
Hex color, e.g. #0057FF
positionenum | null
Optional
bottom-right, bottom-left, top-right or top-left
greeting_messagestring | null
Optional
First message shown to visitors
placeholder_textstring | null
Optional
Input placeholder
brand_namestring | null
Optional
Brand name shown in the header
brand_logo_urlstring | null
Optional
HTTP(S) URL of the brand logo
form_enabledboolean
Optional
Collect visitor details before chatting
form_titlestring | null
Optional
Pre-chat form title
form_fieldsobject[] | null
Optional
Pre-chat form field configs (max 50)
form_submit_labelstring | null
Optional
Pre-chat form button label
allowed_domainsstring[] | null
Optional
Domains allowed to embed the widget (max 50)
is_activeboolean
Optional
Whether the widget is live
launcher_styleenum
Optional
bubble or pill
launcher_labelstring | null
Optional
Label on a pill launcher (max 60 chars)
subtitlestring | null
Optional
Header subtitle (max 140 chars)
themeenum
Optional
auto, light or dark
offset_xinteger
Optional
Horizontal inset in px (0-400)
offset_yinteger
Optional
Vertical inset in px (0-400)
auto_open_delayinteger | null
Optional
Seconds before the widget opens itself (0-600)
video_avatar_agent_iduuid | null
Optional
Agent whose video avatar fronts the widget (video must be enabled)

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/chat_widgets \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Website Chat",
    "ai_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "primary_color": "#6366f1",
    "allowed_domains": ["acme.example"]
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "name": "Website Chat",
    "ai_agent_id": "770e8400-e29b-41d4-a716-446655440000",
    "primary_color": "#6366f1",
    "allowed_domains": [
      "acme.example"
    ],
    "is_active": true,
    "embed_key": "3f9a1c0b7d2e4a68",
    "created_at": "2026-05-04T17:30:00Z",
    "updated_at": "2026-05-04T17:30:00Z"
  }
}
PUT

Update Chat Widget

https://app.aivi.io/ignite/api/v1/chat_widgets/:id
chat_widgets:write

Update a chat widget. Every body field is optional but at least one is required; fields you omit are left unchanged. Accepts the same fields as Create Chat Widget.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The chat widget ID

Request Body

NameTypeRequiredDescription
namestring
Optional
Widget name (max 200 chars)
is_activeboolean
Optional
Whether the widget is live
greeting_messagestring | null
Optional
First message shown to visitors
primary_colorstring | null
Optional
Hex color, e.g. #0057FF
allowed_domainsstring[] | null
Optional
Domains allowed to embed the widget

Example Request

bash
curl -X PUT https://app.aivi.io/ignite/api/v1/chat_widgets/660e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "greeting_message": "Welcome back! Ask us anything." }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "organization_id": "880e8400-e29b-41d4-a716-446655440000",
    "name": "Website Chat",
    "greeting_message": "Welcome back! Ask us anything.",
    "is_active": true,
    "embed_key": "3f9a1c0b7d2e4a68",
    "updated_at": "2026-05-04T18:00:00Z"
  }
}
DELETE

Delete Chat Widget

https://app.aivi.io/ignite/api/v1/chat_widgets/:id
chat_widgets:write

Delete a chat widget. A widget that already has conversations cannot be deleted: the request returns 409 widget_has_conversations. Set is_active to false instead to take it offline.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The chat widget ID

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/chat_widgets/660e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}

Landing Pages

Landing pages are branded prospect pages served at {subdomain}.aivisites.io. This is a low-level CRUD surface for listing, inspecting, lifecycle (status) and brand overrides. Creating a fully designed page end to end is done through the AIVI MCP create_landing_page tool. Reads require landing_pages:read; writes and deletes require landing_pages:write.

The Landing Page Object

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "org_id": "880e8400-e29b-41d4-a716-446655440000",
  "subdomain": "acme-roofing",
  "company_name": "Acme Roofing",
  "logo_url": "https://acme.example/logo.png",
  "favicon_url": null,
  "thumbnail_url": null,
  "primary_color": "#0057FF",
  "secondary_color": null,
  "html_source": "<main>…</main>",
  "html_compiled": "<main>…</main>",
  "scraped_assets": null,
  "reference_urls": [
    "https://acme.example"
  ],
  "industry": "roofing",
  "webhook_url": null,
  "webhook_token": null,
  "chat_widget_embed_key": "3f9a1c0b7d2e4a68",
  "phone_number": "+12025551234",
  "status": "active",
  "created_at": "2026-05-01T12:00:00Z",
  "updated_at": "2026-05-04T08:30:00Z",
  "last_migrated_at": null
}
GET

List Landing Pages

https://app.aivi.io/ignite/api/v1/landing_pages
landing_pages:read

Retrieve a paginated list of landing pages.

Query Parameters

NameTypeRequiredDescription
pageinteger
Optional
Page number Default: 1
page_sizeinteger
Optional
Results per page (max 100) Default: 20
statusenum
Optional
provisioning, active, paused or expired
subdomainstring
Optional
Exact subdomain match
searchstring
Optional
Case-insensitive company_name substring
sortstring
Optional
Sort field and direction. Allowed: created_at, updated_at, subdomain, company_name Default: created_at:desc

Example Request

bash
curl "https://app.aivi.io/ignite/api/v1/landing_pages?status=active" \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "org_id": "880e8400-e29b-41d4-a716-446655440000",
      "subdomain": "acme-roofing",
      "company_name": "Acme Roofing",
      "primary_color": "#0057FF",
      "industry": "roofing",
      "status": "active",
      "created_at": "2026-05-01T12:00:00Z",
      "updated_at": "2026-05-04T08:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 1,
    "total_pages": 1
  }
}
GET

Get Landing Page

https://app.aivi.io/ignite/api/v1/landing_pages/:id
landing_pages:read

Retrieve a single landing page by its unique ID.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The landing page ID

Example Request

bash
curl https://app.aivi.io/ignite/api/v1/landing_pages/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "org_id": "880e8400-e29b-41d4-a716-446655440000",
    "subdomain": "acme-roofing",
    "company_name": "Acme Roofing",
    "logo_url": "https://acme.example/logo.png",
    "primary_color": "#0057FF",
    "reference_urls": [
      "https://acme.example"
    ],
    "industry": "roofing",
    "chat_widget_embed_key": "3f9a1c0b7d2e4a68",
    "phone_number": "+12025551234",
    "status": "active",
    "created_at": "2026-05-01T12:00:00Z",
    "updated_at": "2026-05-04T08:30:00Z",
    "last_migrated_at": null
  }
}
POST

Create Landing Page

https://app.aivi.io/ignite/api/v1/landing_pages
landing_pages:write

Create a landing page row. Subdomain and company_name are required. Reserved subdomains are rejected, and a subdomain already in use returns 409 duplicate_subdomain. For a fully generated page use the MCP create_landing_page tool instead.

Request Body

NameTypeRequiredDescription
subdomainstring
Required
Lowercase letters, digits and hyphens, 3-63 chars
company_namestring
Required
Company name (max 200 chars)
html_sourcestring
Optional
Page HTML source
html_compiledstring
Optional
Sanitized rendered HTML
scraped_assetsobject
Optional
Assets scraped from the reference site
reference_urlsstring[]
Optional
Reference site URLs
logo_urlstring
Optional
HTTP(S) logo URL
favicon_urlstring
Optional
HTTP(S) favicon URL
primary_colorstring
Optional
Hex color, e.g. #0057FF
secondary_colorstring
Optional
Hex color
industrystring
Optional
Industry label (max 60 chars)
chat_widget_embed_keystring
Optional
embed_key of a chat widget in your organization
phone_numberstring
Optional
Contact phone shown on the page
statusenum
Optional
provisioning, active, paused or expired

Example Request

bash
curl -X POST https://app.aivi.io/ignite/api/v1/landing_pages \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{
    "subdomain": "acme-roofing",
    "company_name": "Acme Roofing",
    "primary_color": "#0057FF",
    "status": "active"
  }'

Response
201

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "org_id": "880e8400-e29b-41d4-a716-446655440000",
    "subdomain": "acme-roofing",
    "company_name": "Acme Roofing",
    "primary_color": "#0057FF",
    "reference_urls": [],
    "status": "active",
    "created_at": "2026-05-04T17:30:00Z",
    "updated_at": "2026-05-04T17:30:00Z",
    "last_migrated_at": null
  }
}
PATCH

Update Landing Page

https://app.aivi.io/ignite/api/v1/landing_pages/:id
landing_pages:write

Partially update a landing page. At least one field is required. Nullable fields can be cleared by sending null. The subdomain cannot be changed.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The landing page ID

Request Body

NameTypeRequiredDescription
company_namestring
Optional
Company name (max 200 chars)
logo_urlstring | null
Optional
HTTP(S) logo URL
favicon_urlstring | null
Optional
HTTP(S) favicon URL
primary_colorstring | null
Optional
Hex color
secondary_colorstring | null
Optional
Hex color
industrystring | null
Optional
Industry label (max 60 chars)
chat_widget_embed_keystring | null
Optional
embed_key of a chat widget in your organization, or null to detach
phone_numberstring | null
Optional
Contact phone shown on the page
statusenum
Optional
provisioning, active, paused or expired

Example Request

bash
curl -X PATCH https://app.aivi.io/ignite/api/v1/landing_pages/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "paused" }'

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "org_id": "880e8400-e29b-41d4-a716-446655440000",
    "subdomain": "acme-roofing",
    "company_name": "Acme Roofing",
    "status": "paused",
    "updated_at": "2026-05-04T18:00:00Z"
  }
}
DELETE

Delete Landing Page

https://app.aivi.io/ignite/api/v1/landing_pages/:id
landing_pages:write

Permanently delete a landing page. The subdomain becomes available again.

Path Parameters

NameTypeRequiredDescription
iduuid
Required
The landing page ID

Example Request

bash
curl -X DELETE https://app.aivi.io/ignite/api/v1/landing_pages/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer aivi_sk_your_api_key_here" \
  -H "X-Aivi-Organization: <organization_id>"

Response
200

json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "deleted": true
  }
}