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/v1Content-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/adminget all scopes,membergets read-mostly + send messaging,agentgets read + send messaging only. Multi-org users must include theX-Aivi-Organizationheader to select the active organization.
# 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.
| Name | Type | Required | Description |
|---|---|---|---|
| contacts:read | scope | Optional | List and view contacts |
| contacts:write | scope | Optional | Create and update contacts |
| contacts:delete | scope | Optional | Delete contacts |
| calls:read | scope | Optional | List and view calls, including transcripts and recording URLs |
| messages:read | scope | Optional | List and view SMS/MMS/WhatsApp messages |
| messages:write | scope | Optional | Send SMS messages to contacts |
| emails:read | scope | Optional | List and view sent and received emails |
| emails:write | scope | Optional | Send emails to contacts |
| workflows:read | scope | Optional | List workflows, read workflow detail (including the full graph), and read enrollments |
| workflows:enroll | scope | Optional | Enroll contacts in workflows and cancel enrollments |
| workflows:write | scope | Optional | Create, update, duplicate, activate, and deactivate workflows |
| workflows:delete | scope | Optional | Delete workflows (cascades to nodes, edges, and enrollments) |
| rubrics:read | scope | Optional | List and view QA rubrics and their criteria |
| rubrics:write | scope | Optional | Create new QA rubrics |
| scorecards:read | scope | Optional | List and view QA evaluation scorecards |
| calls:initiate | scope | Optional | Dispatch outbound AI agent calls (POST /calls). Reserves call funds. |
| phone_numbers:read | scope | Optional | List and view the org's purchased phone numbers |
| ai_agents:read | scope | Optional | List and view the org's AI voice agents |
| organizations:read | scope | 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.
# 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:
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.
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:
{
"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
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.
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
{
"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
{
"success": false,
"error": "Human-readable error message",
"code": "machine_readable_code",
"timestamp": "2026-02-26T12:00:00.000Z"
}Error Codes
| Name | Type | Required | Description |
|---|---|---|---|
| 400 | validation_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 |
| 400 | invalid_body | Optional | Request body is not valid JSON |
| 400 | invalid_phones | Optional | The phones list could not be saved |
| 400 | invalid_emails | Optional | The emails list could not be saved |
| 400 | reserved_tag | Optional | The system-managed DNC tag cannot be added or removed via PUT /contacts/:id — use POST/DELETE /contacts/:id/dnc |
| 401 | unauthorized | Optional | Missing or invalid API key / OAuth token |
| 403 | insufficient_scope | Optional | Caller lacks the required scope for this operation |
| 400 | org_required | Optional | OAuth user belongs to multiple orgs; set the X-Aivi-Organization header. Response includes available_organization_ids. |
| 403 | org_membership_required | Optional | OAuth user is not a member of the requested X-Aivi-Organization |
| 404 | not_found | Optional | The requested resource does not exist |
| 409 | duplicate_phone | Optional | A contact with this phone number already exists. Returned as 400 instead when the collision is raised while writing a phones list |
| 409 | duplicate_email | Optional | A contact with this email already exists. Returned as 400 instead when the collision is raised while writing an emails list |
| 402 | insufficient_funds | Optional | Insufficient account funds for a paid operation (e.g. AIVI Insights enrichment) |
| 422 | no_enrichment_data | Optional | Contact lacks sufficient data for enrichment (needs name, phone, email, or address) |
| 429 | rate_limited | Optional | Rate limit exceeded (100 req/min) |
| 500 | server_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.”
https://mcp.aivi.io/mcpAIVI_API_KEY env varConnect via Claude Code
claude mcp add --transport http \
--callback-port 54513 \
aivi https://mcp.aivi.io/mcpThen 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).
List Organizations
https://app.aivi.io/ignite/api/v1/organizationsReturns 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
curl https://app.aivi.io/ignite/api/v1/organizations \
-H "Authorization: Bearer <oauth_access_token>"Response 200
{
"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 Current Organization
https://app.aivi.io/ignite/api/v1/organizations/meReturns the organization the caller is scoped to. Stripe IDs, internal vendor IDs, and KYC PII are not returned.
Example Request
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
{
"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
{
"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"
}List Phone Numbers
https://app.aivi.io/ignite/api/v1/phone_numbersReturns the organization's purchased phone numbers, paginated.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number (default 1). |
| page_size | integer | Optional | Items per page, 1–100 (default 20). |
| is_active | boolean | Optional | Filter to active or inactive numbers. |
| ai_enabled | boolean | Optional | Filter to numbers wired up for AI agent calling. |
| sort | string | Optional | created_at|phone_number, optional :asc / :desc. |
Example Request
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
{
"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 Phone Number
https://app.aivi.io/ignite/api/v1/phone_numbers/:idRetrieve a single phone number by its unique ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The phone number ID |
Example Request
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
{
"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
{
"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"
}List AI Agents
https://app.aivi.io/ignite/api/v1/ai_agentsReturns the organization's AI agents, paginated.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number (default 1). |
| page_size | integer | Optional | Items per page, 1–100 (default 20). |
| status | string | Optional | draft | active | inactive |
| call_direction | string | Optional | inbound | outbound | both |
| search | string | Optional | Case-insensitive name substring match. |
| is_template | boolean | Optional | Filter to template agents. |
| sort | string | Optional | created_at|updated_at|name, optional :asc / :desc. |
Example Request
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
{
"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 AI Agent
https://app.aivi.io/ignite/api/v1/ai_agents/:idRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The AI agent ID |
Example Request
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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| done | string | Optional | The Expert ran for real and completed its SOP. |
| shadow | string | Optional | Dry run — it reported what it would do; no side effects (certified/validated, not yet live). |
| escalate | string | Optional | The SOP reached a step that hands off to a human; execution stopped there. |
| gate_blocked | string | Optional | A scope gate or prerequisite was not satisfied (e.g. identity not verified on this call). Nothing ran. |
| blocked | string | Optional | The Expert is not certified, so it is not invokable. |
| error | string | Optional | A step failed. The result field carries the reason. |
List Experts
https://app.aivi.io/ignite/api/v1/expertsRetrieve a paginated list of the Experts (Skills) in your organization, newest first. Use cert_status to find the ones that are invokable.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| search | string | Optional | Case-insensitive match on the Expert name (1-200 characters) |
| cert_status | string | Optional | Filter by lifecycle state: draft, pending_cert, validated, certified, live, rejected |
Example Request
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
{
"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 Expert
https://app.aivi.io/ignite/api/v1/experts/:idRetrieve a single Expert, including its prerequisites (the conditions that must hold before it runs) and its procedure (the ordered SOP steps).
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The Expert ID |
Example Request
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
{
"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"
}
}
]
}
}Invoke Expert
https://app.aivi.io/ignite/api/v1/experts/:id/invokeRun 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The Expert ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| args | object | 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_id | uuid | Optional | The contact to act on. Required by steps that read or write contact data (identity checks, SMS, field updates). |
| call_id | uuid | 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_id | uuid | Optional | The AI agent whose configuration the steps should use, when relevant. |
Example Request
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique identifier (read-only) |
| first_name | string | Optional | First name |
| last_name | string | Optional | Last name |
| full_name | string | Optional | Computed full name (read-only) |
| string | Optional | Mirror of the primary entry in emails. Writable — writing it updates that entry | |
| phone | string | Optional | Mirror of the primary entry in phones, E.164 format (e.g. +12025551234). Writable — writing it updates that entry |
| secondary_phone | string | 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 |
| phones | object[] | 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 |
| emails | object[] | 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 |
| address | object | Optional | Address object (street, city, state, zip, country) |
| status | string | Optional | One of: active, inactive, scrubbed, followup, deceased |
| tags | string[] | Optional | Array of tag strings |
| custom_fields | object | Optional | Arbitrary key-value pairs |
| assigned_to | uuid | Optional | ID of the assigned user |
| source | string | Optional | Where the contact originated from |
| last_contacted_at | timestamp | Optional | Last interaction time (read-only) |
| created_at | timestamp | Optional | Creation time (read-only) |
| updated_at | timestamp | Optional | Last update time (read-only) |
List Contacts
https://app.aivi.io/ignite/api/v1/contactsRetrieve a paginated list of contacts. Supports filtering by status, tags, search query, and more.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| status | string | Optional | Filter by status (active, inactive, scrubbed, followup, deceased) |
| tags | string | Optional | Filter by tags (comma-separated). Matches contacts with any of the specified tags |
| search | string | 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). |
| string | Optional | Filter by exact email address (case-insensitive). Matches the email mirror only — use search to reach a non-primary address | |
| phone | string | 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_to | uuid | Optional | Filter by assigned user ID |
| sort | string | 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
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
{
"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 Contact
https://app.aivi.io/ignite/api/v1/contacts/:idRetrieve a single contact by its unique ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Example Request
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
{
"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
}
]
}
}Create Contact
https://app.aivi.io/ignite/api/v1/contactsCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| phone | string | Optional | Phone in E.164 format (e.g. +12025551234). Becomes the primary entry of the contact’s phone list. Cannot be combined with phones |
| string | Optional | Email address. Becomes the primary entry of the contact’s email list. Cannot be combined with emails | |
| first_name | string | Optional | First name |
| last_name | string | Optional | Last name |
| secondary_phone | string | 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 |
| address | object | Optional | Address object |
| status | string | Optional | Contact status (active, inactive, scrubbed, followup, deceased) |
| tags | string[] | Optional | Comma-separated tags |
| custom_fields | object | Optional | Custom key-value pairs |
| assigned_to | uuid | Optional | User ID to assign the contact to |
| source | string | Optional | Origin source of the contact |
| trigger_flows_on_duplicates | boolean | 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 |
| phones | object[] | 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 |
| emails | object[] | 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
# 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
{
"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
}
]
}
}Update Contact
https://app.aivi.io/ignite/api/v1/contacts/:idUpdate 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| first_name | string | Optional | First name |
| last_name | string | Optional | Last name |
| string | null | Optional | Email address — updates the primary entry of the email list (null to clear). Cannot be combined with emails | |
| phone | string | null | Optional | Phone in E.164 format — updates the primary entry of the phone list (null to clear). Cannot be combined with phones |
| secondary_phone | string | null | Optional | DEPRECATED — prefer phones. Updates the highest-ranked non-primary entry of the phone list (null to clear). Cannot be combined with phones |
| address | object | null | Optional | Address object (null to clear) |
| status | string | Optional | Contact status |
| tags | string[] | Optional | Add tags (merged with existing) |
| custom_fields | object | Optional | Merge custom fields (additive) |
| assigned_to | uuid | null | Optional | User ID or null to unassign |
| source | string | null | Optional | Origin source |
| phones | object[] | 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 |
| emails | object[] | 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
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
{
"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 Contact
https://app.aivi.io/ignite/api/v1/contacts/:idSoft-delete a contact. The contact will no longer appear in list results but its data is retained internally.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Example Request
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
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"deleted": true
}
}List DNC Records
https://app.aivi.io/ignite/api/v1/contacts/:id/dncReturn 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Example Request
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
{
"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
}
]
}Mark Contact DNC
https://app.aivi.io/ignite/api/v1/contacts/:id/dncManually 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| reason | enum | Optional | `manual` (default) or `complaint` |
| notes | string | Optional | Free-form note shown in the audit trail (max 2000 chars) |
Example Request
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
{
"success": true,
"data": {
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"dnc_record_id": "a1b2c3d4-...",
"reason": "manual"
}
}Revoke DNC
https://app.aivi.io/ignite/api/v1/contacts/:id/dncMark 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| reason | string | Required | Why the DNC is being lifted. Stored on every revoked row. |
Example Request
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
{
"success": true,
"data": {
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"revoked_count": 2
}
}Suppress one phone or email
https://app.aivi.io/ignite/api/v1/contacts/:id/phones/:point_id/dncStop 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
| point_id | uuid | Required | The phones[].id / emails[].id of the channel to suppress |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| reason | string | Optional | 'manual' (default) or 'complaint' |
| notes | string | Optional | Free-text context stored on the audit row |
Example Request
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
{
"success": true,
"data": {
"contact_id": "550e8400-e29b-41d4-a716-446655440000",
"point_id": "770e8400-e29b-41d4-a716-446655440000",
"dnc_record_id": "a1b2c3d4-...",
"reason": "manual"
}
}Un-suppress one phone or email
https://app.aivi.io/ignite/api/v1/contacts/:id/phones/:point_id/dncRevoke 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The contact ID |
| point_id | uuid | Required | The phones[].id / emails[].id to un-suppress |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| reason | string | Required | Why the suppression is being lifted. Stored on every revoked row. |
Example Request
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique identifier (read-only) |
| contact_id | uuid | null | Optional | Associated contact, or null if not linked |
| direction | enum | Optional | inbound or outbound |
| status | enum | Optional | initiated, ringing, in-progress, completed, busy, failed, no-answer, cancelled |
| outcome | enum | null | Optional | answered, no-answer, busy, voicemail, failed, canceled, transferred (set after the call completes) |
| phone_number | string | null | Optional | Primary phone associated with the call |
| from_number | string | null | Optional | Caller number (E.164) |
| to_number | string | null | Optional | Recipient number (E.164) |
| initiated_at | timestamp | null | Optional | When the call was initiated |
| started_at | timestamp | null | Optional | When the call connected |
| ended_at | timestamp | null | Optional | When the call ended |
| duration | integer | Optional | Call length in seconds |
| call_summary | string | null | Optional | AI-generated plain-text summary (post-call pipeline) |
| source | enum | Optional | phone (real telephony) or upload (manual evaluation upload) |
| transcript_text | string | null | Optional | Full transcript as plain text. Detail endpoint only. |
| transcript_object | array | null | Optional | Structured transcript: array of {role, content} objects. Detail endpoint only. |
| call_intelligence | object | Optional | AI conversation intelligence: summary, sentiment, category, action_items, topics, key_quotes, quality_metrics. Detail endpoint only. |
| recording_url | string | null | Optional | Signed recording URL valid for 1 hour. Re-fetch the call for a fresh URL. Detail endpoint only. |
| recording_duration | integer | null | Optional | Recording length in seconds. Detail endpoint only. |
| created_at | timestamp | Optional | Creation time (read-only) |
| updated_at | timestamp | Optional | Last update time (read-only) |
List Calls
https://app.aivi.io/ignite/api/v1/callsRetrieve a paginated list of calls. Returns the light payload — transcripts, intelligence, and recording URLs are only available via the detail endpoint.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| contact_id | uuid | Optional | Filter by associated contact |
| direction | string | Optional | Filter by direction (inbound, outbound) |
| status | string | Optional | Filter by call status |
| outcome | string | Optional | Filter by outcome (answered, no-answer, busy, voicemail, failed, canceled, transferred) |
| from_date | timestamp | Optional | Lower bound on created_at (ISO 8601) |
| to_date | timestamp | Optional | Upper bound on created_at (ISO 8601) |
| sort | string | Optional | Sort field and direction. Allowed: created_at, started_at, ended_at, duration Default: created_at:desc |
Example Request
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
{
"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 Call
https://app.aivi.io/ignite/api/v1/calls/:idRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The call ID |
Example Request
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
{
"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"
}
}Initiate Call
https://app.aivi.io/ignite/api/v1/callsDispatch 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
| Name | Type | Required | Description |
|---|---|---|---|
| agent_id | uuid | Required | AI agent that will run the call. Must belong to your organization. |
| to_number | string (E.164) | Required | Destination phone number, e.g. +14155551234. |
| from_phone_number_id | uuid | Required | ID from /phone_numbers. Must be active and AI-enabled. |
| contact_id | uuid | Optional | Optional contact this call is associated with. |
| system_prompt_override | string | Optional | Replace the agent's system prompt for this call only. |
| greeting_override | string | Optional | Replace the agent's greeting for this call only. |
| metadata | object | Optional | Free-form metadata merged into the call record. |
Example Request
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
{
"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 Call Intelligence
https://app.aivi.io/ignite/api/v1/call_intelligence/:call_idRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| call_id | uuid | Required | The call ID |
Example Request
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique identifier (read-only) |
| contact_id | uuid | null | Optional | Associated contact, or null if not linked |
| conversation_id | uuid | Optional | Conversation thread the message belongs to |
| direction | enum | Optional | inbound or outbound |
| channel | enum | Optional | sms, mms, or whatsapp |
| status | enum | Optional | accepted, queued, sent, delivered, undelivered, failed, received |
| from_number | string | Optional | Sender number (E.164) |
| to_number | string | Optional | Recipient number (E.164) |
| body | string | Optional | Message body (plain text) |
| media_urls | string[] | 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_read | boolean | Optional | Whether the message has been read in the dashboard |
| sent_at | timestamp | null | Optional | When the message was sent |
| delivered_at | timestamp | null | Optional | When delivery was confirmed by the carrier |
| read_at | timestamp | null | Optional | When the message was first read |
| message_sid | string | null | Optional | Provider-side message ID (e.g. Twilio SID) |
| created_at | timestamp | Optional | Creation time (read-only) |
| updated_at | timestamp | Optional | Last update time (read-only) |
List Messages
https://app.aivi.io/ignite/api/v1/messagesRetrieve a paginated list of SMS, MMS, and WhatsApp messages. Filter by contact, conversation, channel, direction, or date range.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| contact_id | uuid | Optional | Filter by associated contact |
| conversation_id | uuid | Optional | Filter by conversation thread |
| channel | string | Optional | Filter by channel (sms, mms, whatsapp) |
| direction | string | Optional | Filter by direction (inbound, outbound) |
| from_date | timestamp | Optional | Lower bound on created_at (ISO 8601) |
| to_date | timestamp | Optional | Upper bound on created_at (ISO 8601) |
| sort | string | Optional | Sort field and direction. Allowed: created_at, sent_at, delivered_at Default: created_at:desc |
Example Request
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
{
"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 Message
https://app.aivi.io/ignite/api/v1/messages/:idRetrieve a single message by its unique ID.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The message ID |
Example Request
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
{
"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"
}
}Send Message
https://app.aivi.io/ignite/api/v1/messagesSend 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
| Name | Type | Required | Description |
|---|---|---|---|
| contact_id | uuid | Required | The recipient contact (must have a phone number) |
| from_number | string | Required | Sender phone number in E.164 format. Must be one of your organization's verified Twilio numbers. |
| body | string | Required | Message text (1-1600 characters) |
| media_urls | string[] | 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
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique identifier (read-only) |
| contact_id | uuid | null | Optional | Associated contact, or null if not linked |
| conversation_id | uuid | null | Optional | Conversation thread the email belongs to |
| direction | enum | Optional | inbound or outbound |
| status | enum | Optional | queued, sent, delivered, opened, clicked, bounced, dropped, failed |
| from_email | string | Optional | Sender email address |
| from_name | string | null | Optional | Sender display name |
| to_email | string | Optional | Recipient email address |
| reply_to | string | null | Optional | Reply-To header (if different from from_email) |
| subject | string | Optional | Email subject |
| sent_at | timestamp | null | Optional | When the email was handed off to the provider |
| delivered_at | timestamp | null | Optional | When the recipient mail server accepted delivery |
| opened_at | timestamp | null | Optional | When the email was first opened |
| clicked_at | timestamp | null | Optional | When a link in the email was first clicked |
| open_count | integer | Optional | Total number of opens recorded |
| click_count | integer | Optional | Total number of link clicks recorded |
| is_read | boolean | Optional | Whether the email has been read in the dashboard |
| sendgrid_message_id | string | null | Optional | Provider-side message ID |
| body | string | null | Optional | Plain-text body. Detail endpoint only. |
| html_body | string | null | Optional | HTML body. Detail endpoint only. |
| cc | string[] | null | Optional | CC recipients. Detail endpoint only. |
| bcc | string[] | null | Optional | BCC recipients. Detail endpoint only. |
| bounced_at | timestamp | null | Optional | When the email bounced. Detail endpoint only. |
| error_message | string | null | Optional | Provider error if delivery failed. Detail endpoint only. |
| error_code | string | null | Optional | Provider error code. Detail endpoint only. |
| created_at | timestamp | Optional | Creation time (read-only) |
| updated_at | timestamp | Optional | Last update time (read-only) |
List Emails
https://app.aivi.io/ignite/api/v1/emailsRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| contact_id | uuid | Optional | Filter by associated contact |
| conversation_id | uuid | Optional | Filter by conversation thread |
| direction | string | Optional | Filter by direction (inbound, outbound) |
| status | string | Optional | Filter by status (queued, sent, delivered, opened, clicked, bounced, dropped, failed) |
| from_date | timestamp | Optional | Lower bound on created_at (ISO 8601) |
| to_date | timestamp | Optional | Upper bound on created_at (ISO 8601) |
| sort | string | Optional | Sort field and direction. Allowed: created_at, sent_at, delivered_at Default: created_at:desc |
Example Request
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
{
"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 Email
https://app.aivi.io/ignite/api/v1/emails/:idRetrieve a single email by its unique ID. Includes the plain-text body, HTML body, CC/BCC recipients, and any delivery error details.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The email ID |
Example Request
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
{
"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"
}
}Send Email
https://app.aivi.io/ignite/api/v1/emailsSend 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
| Name | Type | Required | Description |
|---|---|---|---|
| contact_id | uuid | Required | The recipient contact (must have an email address) |
| subject | string | Required | Email subject (1-998 characters) |
| body | string | Required | Plain-text body. If html_body is omitted, this is converted to safe HTML automatically. |
| html_body | string | Optional | HTML body. Overrides the auto-conversion of body. |
| cc | string[] | Optional | CC recipients (array of email addresses, max 50) |
| bcc | string[] | Optional | BCC recipients (array of email addresses, max 50) |
| reply_to | string | Optional | Reply-To header. Overrides the sender's default reply_to. |
| from_sender_id | uuid | Optional | A specific sender from email_sender_configs. Omit to use your org's default sender. |
Example Request
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique identifier (read-only) |
| name | string | Optional | Display name |
| description | string | null | Optional | Optional description |
| status | enum | Optional | active, completed, failed, draft, archived |
| is_active | boolean | Optional | Whether the workflow is enabled. Both status=active AND is_active=true are required for enrollment. |
| trigger_type | string | Optional | What kicks the workflow off: manual, contact_created, contact_updated, webhook, date_time, etc. |
| is_ai_autopilot | boolean | Optional | Whether this is an AI-autopilot workflow (ML-driven node selection) |
| created_at | timestamp | Optional | Creation time (read-only) |
| updated_at | timestamp | Optional | Last update time (read-only) |
List Workflows
https://app.aivi.io/ignite/api/v1/workflowsList 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
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| status | string | Optional | Filter by workflow status (active, completed, failed, draft, archived) |
| include_inactive | boolean | Optional | Include drafts, archived, and other non-enrollable workflows Default: false |
| sort | string | Optional | Sort field and direction. Allowed: created_at, updated_at, name Default: created_at:desc |
Example Request
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
{
"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 Workflow
https://app.aivi.io/ignite/api/v1/workflows/:idRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to retrieve |
Example Request
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
{
"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": {}
}
]
}
}Create Workflow
https://app.aivi.io/ignite/api/v1/workflowsCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Workflow name (1–255 chars) |
| description | string | Optional | Optional description (max 2000) |
| trigger | object | Required | {type, config?} — type from manual | contact_created | contact_updated | webhook | date_time | meta_lead | qa_evaluation.completed | moment_detected |
| timezone | string | Optional | IANA timezone (e.g. America/New_York). Defaults to org timezone when omitted. |
| nodes | array | Required | Node objects with {node_key, node_type, action_type?, label?, config?, position_x?, position_y?} |
| edges | array | Required | Edge objects with {edge_key, source_node_key, target_node_key, source_handle?, target_handle?, label?, config?} |
Example Request
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
{
"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": {}
}
]
}
}Update Workflow
https://app.aivi.io/ignite/api/v1/workflows/:idAtomic 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to update |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Workflow name |
| trigger | object | Required | {type, config?} |
| nodes | array | Required | Full replacement node list |
| edges | array | Required | Full replacement edge list |
| description | string | Optional | Optional description |
| timezone | string | Optional | IANA timezone |
Example Request
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
{
"success": true,
"data": {
"id": "990e8400-e29b-41d4-a716-446655440000",
"name": "Welcome Sequence (v2)",
"status": "active",
"is_active": true
}
}Delete Workflow
https://app.aivi.io/ignite/api/v1/workflows/:idHard-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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to delete |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| force | boolean | Optional | Set true to delete despite active enrollments (cascades to enrollment history) Default: false |
Example Request
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
{
"success": true,
"data": {
"id": "990e8400-e29b-41d4-a716-446655440000",
"deleted": true
}
}Activate Workflow
https://app.aivi.io/ignite/api/v1/workflows/:id/activateRe-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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to activate |
Example Request
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
{
"success": true,
"data": {
"id": "990e8400-e29b-41d4-a716-446655440000",
"status": "active",
"is_active": true
}
}Deactivate Workflow
https://app.aivi.io/ignite/api/v1/workflows/:id/deactivateFlip 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to deactivate |
Example Request
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
{
"success": true,
"data": {
"id": "990e8400-e29b-41d4-a716-446655440000",
"status": "draft",
"is_active": false
}
}Duplicate Workflow
https://app.aivi.io/ignite/api/v1/workflows/:id/duplicateClone 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to duplicate |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | Override the duplicate workflow name |
Example Request
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
{
"success": true,
"data": {
"id": "bb0e8400-e29b-41d4-a716-446655440222",
"name": "Welcome Sequence — Sandbox",
"status": "draft",
"is_active": false
}
}Enroll Contact in Workflow
https://app.aivi.io/ignite/api/v1/workflows/:id/enrollEnroll 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The workflow ID to enroll into |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| contact_id | uuid | Required | The contact to enroll |
Example Request
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
{
"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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Optional | Unique enrollment ID (read-only) |
| workflow_id | uuid | Optional | The workflow this enrollment is executing |
| contact_id | uuid | null | Optional | The contact being enrolled (null for webhook-triggered runs) |
| status | enum | Optional | active, completed, failed, paused, looping, skipped, cancelled, paused_insufficient_funds |
| enrolled_at | timestamp | Optional | When the enrollment was created |
| started_at | timestamp | Optional | When execution started |
| completed_at | timestamp | null | Optional | When the enrollment finished (success or failure) |
| paused_at | timestamp | null | Optional | When the enrollment was paused (e.g. waiting for an event) |
| paused_at_node | string | null | Optional | Node key where execution paused |
| current_node_key | string | null | Optional | Currently active workflow node |
| current_step | integer | Optional | Steps completed so far |
| total_steps | integer | Optional | Total steps in the workflow graph |
| last_executed_at | timestamp | null | Optional | When the last node ran |
| error_message | string | null | Optional | Error message if status is failed |
List Enrollments
https://app.aivi.io/ignite/api/v1/workflow_enrollmentsList workflow enrollments in your organization. Filter by contact, workflow, status, or date range.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| contact_id | uuid | Optional | Filter by enrolled contact |
| workflow_id | uuid | Optional | Filter by workflow |
| status | string | Optional | Filter by enrollment status |
| from_date | timestamp | Optional | Lower bound on enrolled_at (ISO 8601) |
| to_date | timestamp | Optional | Upper bound on enrolled_at (ISO 8601) |
| sort | string | Optional | Sort field and direction. Allowed: enrolled_at, started_at, completed_at, last_executed_at Default: enrolled_at:desc |
Example Request
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
{
"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
}
}Cancel Enrollment
https://app.aivi.io/ignite/api/v1/workflow_enrollments/:idCancel 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
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The enrollment ID |
Example Request
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
{
"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
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Stable identifier for the criterion (any non-empty string) |
| name | string | Required | Short label shown in scorecards |
| description | string | Required | What this criterion measures |
| weight | number | Required | Relative weight (0-100). Weights are normalized at scoring time. |
| max_score | number | Required | Maximum points achievable for this criterion (1-100) |
| evaluation_guidance | string | Required | Instructions for the AI scorer on how to evaluate this criterion |
| type | string | Required | Either "boolean" (pass/fail) or "scale" (graded 0-max_score) |
| required | boolean | Required | Whether this criterion must be addressed in every call |
List Rubrics
https://app.aivi.io/ignite/api/v1/rubricsRetrieve a paginated list of QA rubrics for your organization. Returns the full rubric including criteria.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| agent_type | string | Optional | Filter by agent type (ai, live, both) |
| is_active | boolean | Optional | Filter by active flag |
| auto_evaluate | boolean | Optional | Filter by whether the rubric auto-runs on call completion |
| sort | string | Optional | Sort field and direction. Allowed: created_at, updated_at, name Default: created_at:desc |
Example Request
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
{
"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 Rubric
https://app.aivi.io/ignite/api/v1/rubrics/:idRetrieve a single rubric by its unique ID, including the full criterion list.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The rubric ID |
Example Request
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
{
"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"
}
}Create Rubric
https://app.aivi.io/ignite/api/v1/rubricsCreate 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
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | 1-200 characters |
| description | string | Optional | Optional context shown in dashboards (max 2000 characters) |
| criteria | array | Required | Array of criterion objects (1-50). See "Criterion Shape" above. |
| agent_type | string | Optional | ai, live, or both Default: both |
| is_active | boolean | Optional | Whether the rubric is enabled Default: true |
| auto_evaluate | boolean | Optional | Whether to score every matching call automatically Default: true |
Example Request
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
{
"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.
List Scorecards
https://app.aivi.io/ignite/api/v1/scorecardsRetrieve 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
| Name | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number Default: 1 |
| page_size | integer | Optional | Results per page (max 100) Default: 20 |
| call_id | uuid | Optional | Filter by call |
| rubric_id | uuid | Optional | Filter by rubric |
| result | string | Optional | Filter by result (pass, fail, review) |
| evaluator_type | string | Optional | Filter by evaluator (ai, human, hybrid) |
| min_percentage | number | Optional | Inclusive lower bound on percentage_score (0-100) |
| max_percentage | number | Optional | Inclusive upper bound on percentage_score (0-100) |
| from_date | timestamp | Optional | Lower bound on created_at (ISO 8601) |
| to_date | timestamp | Optional | Upper bound on created_at (ISO 8601) |
| sort | string | Optional | Sort field and direction. Allowed: created_at, percentage_score Default: created_at:desc |
Example Request
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
{
"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 Scorecard
https://app.aivi.io/ignite/api/v1/scorecards/:idRetrieve a single scorecard by its unique ID, including the per-criterion breakdown and AI notes.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | uuid | Required | The scorecard ID |
Example Request
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
{
"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"
}
}