AIVIAPI Reference

Getting Started

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

Organizations

  • Overview
  • GETList Organizations
  • GETGet Current Organization

Phone Numbers

  • Overview
  • GETList Phone Numbers
  • GETGet Phone Number

AI Agents

  • Overview
  • GETList AI Agents
  • GETGet 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

Calls

  • Overview
  • GETList Calls
  • GETGet Call
  • POSTInitiate Call

Call Intelligence

  • Overview
  • GETGet Call Intelligence

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

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

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 /v1/auth/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
ai_agents:readscope
Optional
List and view the org's AI voice agents
organizations:readscope
Optional
View the current organization

Multi-Organization Users

API keys are bound to a single organization at creation time and don't need any additional headers.

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. If you exceed this limit, you will receive a 429 response.

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

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
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). The Organizations endpoints let you introspect membership and the active org.

GET /organizations lists every org the caller belongs to (or the single org an API key is bound to) — 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/mereturns the currently-active org once you've picked one (or for single-org/API-key callers).

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. API keys see the single org the key is bound to (with role='api_key').

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"
  }
}

Phone Numbers

Phone numbers are the Twilio numbers your organization owns. They're used for inbound and outbound voice + SMS. The API exposes them read-only — to purchase or release numbers, use the dashboard.

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"
  }
}

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. The API exposes them read-only — to create or edit agents, use the dashboard.

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": "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": "gemini",
  "model_name": "gemini-2.0-flash",
  "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"
}
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
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": "gemini",
      "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": "gemini",
    "model_name": "gemini-2.0-flash",
    "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"
  }
}

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). The contact's denormalized is_dnc flag is true when at least one row has revoked_at = null. Outreach endpoints (initiate-ai-call, send-sms, send-email) return 403 with code DNC_BLOCKED for is_dnc contacts.

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 to this contact is blocked immediately. Idempotent — repeated calls with the same reason return the existing record. Reasons litigator, caller_request, and sms_stop are reserved for AIVI's automated detection paths; 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)

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 while the contact's other channels 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 in the contact's phones[] / emails[] array, and outreach to that channel returns 403 DNC_BLOCKED. Idempotent per (channel, reason).

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

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.

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
  }
}

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
Signed recording URL valid for 1 hour. Re-fetch the call for a fresh URL. 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).

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",
    "provider": "rapida"
  }
}

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"
  }
}

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. Activating a workflow re-runs validation; an Update that produces a malformed graph saves successfully but blocks activation.

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"
  }
}