AI Call API v1

Phone calls for AI agents: register for a person, book calls, watch the transcript live, coach, transfer, hang up, get the transcript back. Machine-readable spec: /api/v1/openapi.json

Base URL https://aicall.prodevgroup.com/api/v1

Authentication Every request (except these docs and the agent registration endpoints) needs Authorization: Bearer aic_live_.... Tokens are granted to an AI agent by the person it works for. Each token has scopes: calls:book_approved, calls:book_new, calls:coach, calls:transfer, calls:hangup, calls:listen, calls:vault.

AI agents without a token Register for a person in seconds: POST /api/v1/agent-access/requests with their email, their name and the permissions you need. You get your token and its ID at once. It answers 403 token_pending_approval until they approve on a one-page form (Permission | Asked | Approve | Deny); then the same token is live with exactly what they approved. Nothing to pick up. Guide: /for-agents or GET /api/v1/agent-access.

Errors are always {"error":{"code":"...","message":"..."}}. Common codes: invalid_request (400), unauthorized / token_expired (401), token_pending_approval / account_pending_approval / insufficient_scope / engine_not_allowed / outbound_only (403), not_found / no_account (404), outside_allowed_hours (409), rate_limited (429, see Retry-After), limit_reached (429: the account's call minutes for the day or month), email_failed (502), no_open_question / call_ended / call_not_connected / transfer_unavailable / no_owner_phone / token_expires_first / too_many_scheduled / idempotency_conflict (409), transfer_target_not_approved (403).

Idempotency call_book requires idempotencyKey and call_note requires commandId; transfers and hang-ups take an optional commandId. Repeating one returns the first result; nothing is dialed or sent twice.

Calls Book a call (it dials within seconds), follow it with call_wait, coach it with call_note, answer the voice agent's questions with call_answer, hand it to the person or an approved number with call_transfer, end it with call_hangup and get the transcript with call_result. Every booking has a live call page (callLink). call_transfer works on answered calls placed in LiveKit rooms: soft tells the caller to hold a moment, instant goes straight to hold music, and if the target does not answer within 25 seconds the agent comes back and carries on. Book it for later with scheduledAt: you are woken at wakeAt by the call.wake webhook (or your own cron, with the wakePrompt it returns), requireSeat holds the dial until you are back on call_wait, and call_upcoming lists your open calls with the seatBrief you left yourself. For “give me a call”, use call_me: it rings your person, they reach whoever they want from that call (their keypad, dial, or call_dial from you), it is recorded and transcribed live while you help in the background, and ** on their keypad or call_hold has the voice agent take a hold for them; call_mute keeps the voice agent listening without a word. For “get me on the phone with John” or “hold with the IRS for me”, use call_pickup (or call_book.handoff): it reaches the person, or holds until a live person answers, then hands the call over. Every call follows its voicemail rule: leave a message (default), hang up, or transfer. Set up any call quickly from a template (call_templates, call_from_template, call_template_save), and keep the account's call profile current with call_profile_update.

Usage GET /api/v1/usage?month=YYYY-MM reports the minutes used by the person behind your token in a UTC month (default: the current one): totals, by call engine and per token. Each call has three legs, rounded up to whole minutes per call: callee (answered to ended), transfer (target connected to ended) and agent (the voice agent on the line). call_result carries the same three numbers for one call as minutes. Minutes are counted, not priced.

MCP AI agents can use the same API through the MCP server at https://aicall.prodevgroup.com/mcp (Streamable HTTP) with the same bearer token.

Agent access

GET/api/v1/agent-accesspublic

Guide for AI agents: how to register for a person in seconds (no key needed). Plain JSON explaining the whole process: ask by email, you get your token at once, the person approves on a one-page form and the same token goes live. Also at /for-agents (HTML) and /llms.txt (Markdown).

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/agent-access"
POST/api/v1/agent-access/requestspublic

Ask a person for permission to act on their behalf (no API key needed); the token is returned right away. For agents with or without a token. You get apiKey and tokenId in this response immediately. The token does nothing (every call answers 403 token_pending_approval) until the person, who gets an email with a one-page approval, ticks the permissions and approves. Then it goes live with exactly the approved permissions; poll pollUrl or just retry. Works even if the person never logs in. Rate limited per IP and per email.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/agent-access/requests" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "name": "Jane Doe", "agentName": "Jane'\''s assistant", "requestedScopes": ["calls:book_new", "calls:coach", "calls:hangup"]}'
Request body schema
{
  "properties": {
    "action": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "new_account",
            "new_token",
            "rotate_token",
            "update_permissions",
            "remove_tokens"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Action",
      "description": "What you ask for. Default: new_account when the email has no account yet, otherwise new_token. rotate_token/update_permissions need tokenId; remove_tokens takes tokenIds (omit to let the person pick). Each sends the person a one-page form."
    },
    "tokenId": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenid",
      "description": "For rotate_token / update_permissions: the token ID (never the token itself)"
    },
    "tokenIds": {
      "anyOf": [
        {
          "items": {
            "type": "string",
            "maxLength": 80
          },
          "type": "array",
          "maxItems": 100
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenids",
      "description": "For remove_tokens: token IDs to remove"
    },
    "email": {
      "type": "string",
      "title": "Email",
      "description": "The person who will own the token and approve by email"
    },
    "name": {
      "type": "string",
      "maxLength": 120,
      "minLength": 1,
      "title": "Name",
      "description": "The person's name (required; used in the greeting and for their account)"
    },
    "agentName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80,
          "minLength": 2
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentname",
      "description": "How the assistant introduces itself (optional; default \"<Name>'s AI assistant\"); the person sees this, e.g. \"Lori (front desk)\""
    },
    "agentEmail": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentemail",
      "description": "The assistant's own email: where call links and transcripts for its calls are sent (optional)"
    },
    "purpose": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ],
      "title": "Purpose"
    },
    "requestedScopes": {
      "anyOf": [
        {
          "items": {
            "type": "string",
            "enum": [
              "calls:book_approved",
              "calls:book_new",
              "calls:coach",
              "calls:transfer",
              "calls:hangup",
              "calls:listen",
              "calls:vault"
            ]
          },
          "type": "array",
          "minItems": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Requestedscopes",
      "description": "Permissions the agent asks for. Required for new_account, new_token and update_permissions"
    },
    "note": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "title": "Note",
      "description": "Personal wording from the agent, shown in the email and on the approval page"
    },
    "temporary": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Temporary",
      "description": "Ask for a temporary token (new_account / new_token). Default lifetime 24 hours; set validForHours to choose."
    },
    "validForHours": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 168.0,
          "minimum": 1.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Validforhours",
      "description": "Temporary token lifetime in whole hours after the person approves, 1 to 168 (one week). Implies temporary. Ask again as often as you need; each approval is one tap."
    }
  },
  "type": "object",
  "required": [
    "email",
    "name"
  ],
  "title": "AccessRequestBody"
}
Response schema (201)
{
  "properties": {
    "id": {
      "type": "string",
      "title": "Id"
    },
    "action": {
      "type": "string",
      "enum": [
        "new_account",
        "new_token",
        "rotate_token",
        "update_permissions",
        "remove_tokens"
      ],
      "title": "Action"
    },
    "status": {
      "type": "string",
      "enum": [
        "PENDING",
        "APPROVED",
        "DECLINED",
        "EXPIRED"
      ],
      "title": "Status"
    },
    "email": {
      "type": "string",
      "title": "Email"
    },
    "agentName": {
      "type": "string",
      "title": "Agentname"
    },
    "agentEmail": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentemail"
    },
    "tokenId": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenid"
    },
    "tokenIds": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Tokenids"
    },
    "requestedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Requestedscopes"
    },
    "grantedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Grantedscopes"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "expiresAt": {
      "type": "string",
      "title": "Expiresat"
    },
    "approvedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Approvedat"
    },
    "temporary": {
      "type": "boolean",
      "title": "Temporary",
      "description": "Temporary token: stops on its own validForHours after approval"
    },
    "validForHours": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Validforhours"
    },
    "tokenExpiresAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenexpiresat",
      "description": "Once active: when a temporary token stops"
    },
    "apiKey": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Apikey",
      "description": "The token, shown once at creation. Inert (403 token_pending_approval) until the person approves."
    },
    "tokenStatus": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "PENDING",
            "ACTIVE",
            "REVOKED"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenstatus"
    },
    "pollSecret": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollsecret",
      "description": "Shown once"
    },
    "pollUrl": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollurl",
      "description": "Shown once; poll it every 30 to 60 s"
    }
  },
  "type": "object",
  "required": [
    "id",
    "action",
    "status",
    "email",
    "agentName",
    "tokenId",
    "tokenIds",
    "requestedScopes",
    "grantedScopes",
    "createdAt",
    "expiresAt",
    "approvedAt",
    "temporary",
    "validForHours",
    "tokenExpiresAt"
  ],
  "title": "AccessRequestView"
}
GET/api/v1/agent-access/requests/{request_id}public

Status of an access request (with its poll secret). Use the pollUrl from the creation response. When status is APPROVED and tokenStatus ACTIVE, the token you already hold is live. DECLINED or EXPIRED (7 days) means no.

Parameters

nameintypedescription
request_idpathrequiredstring
secretqueryrequiredstring

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/agent-access/requests/<request_id>?secret=<secret>"
Response schema (200)
{
  "properties": {
    "id": {
      "type": "string",
      "title": "Id"
    },
    "action": {
      "type": "string",
      "enum": [
        "new_account",
        "new_token",
        "rotate_token",
        "update_permissions",
        "remove_tokens"
      ],
      "title": "Action"
    },
    "status": {
      "type": "string",
      "enum": [
        "PENDING",
        "APPROVED",
        "DECLINED",
        "EXPIRED"
      ],
      "title": "Status"
    },
    "email": {
      "type": "string",
      "title": "Email"
    },
    "agentName": {
      "type": "string",
      "title": "Agentname"
    },
    "agentEmail": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentemail"
    },
    "tokenId": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenid"
    },
    "tokenIds": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Tokenids"
    },
    "requestedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Requestedscopes"
    },
    "grantedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Grantedscopes"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "expiresAt": {
      "type": "string",
      "title": "Expiresat"
    },
    "approvedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Approvedat"
    },
    "temporary": {
      "type": "boolean",
      "title": "Temporary",
      "description": "Temporary token: stops on its own validForHours after approval"
    },
    "validForHours": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Validforhours"
    },
    "tokenExpiresAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenexpiresat",
      "description": "Once active: when a temporary token stops"
    },
    "apiKey": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Apikey",
      "description": "The token, shown once at creation. Inert (403 token_pending_approval) until the person approves."
    },
    "tokenStatus": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "PENDING",
            "ACTIVE",
            "REVOKED"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenstatus"
    },
    "pollSecret": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollsecret",
      "description": "Shown once"
    },
    "pollUrl": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollurl",
      "description": "Shown once; poll it every 30 to 60 s"
    }
  },
  "type": "object",
  "required": [
    "id",
    "action",
    "status",
    "email",
    "agentName",
    "tokenId",
    "tokenIds",
    "requestedScopes",
    "grantedScopes",
    "createdAt",
    "expiresAt",
    "approvedAt",
    "temporary",
    "validForHours",
    "tokenExpiresAt"
  ],
  "title": "AccessRequestView"
}
POST/api/v1/agent-access/changesscope: any active token

Ask the token owner to change this token's permissions. Emails the person who owns the calling token a one-page approval with the new permissions. On approval the token's permissions change in place. Any scope is accepted here; the person decides. 3 per minute per token.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/agent-access/changes" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"requestedScopes": ["calls:book_new", "calls:coach", "calls:transfer"]}'
Request body schema
{
  "properties": {
    "requestedScopes": {
      "items": {
        "type": "string",
        "enum": [
          "calls:book_approved",
          "calls:book_new",
          "calls:coach",
          "calls:transfer",
          "calls:hangup",
          "calls:listen",
          "calls:vault"
        ]
      },
      "type": "array",
      "minItems": 1,
      "title": "Requestedscopes",
      "description": "The permissions you want from now on"
    },
    "purpose": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ],
      "title": "Purpose"
    },
    "note": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "title": "Note"
    },
    "agentName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80,
          "minLength": 2
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentname"
    }
  },
  "type": "object",
  "required": [
    "requestedScopes"
  ],
  "title": "ChangeRequestBody"
}
Response schema (201)
{
  "properties": {
    "id": {
      "type": "string",
      "title": "Id"
    },
    "action": {
      "type": "string",
      "enum": [
        "new_account",
        "new_token",
        "rotate_token",
        "update_permissions",
        "remove_tokens"
      ],
      "title": "Action"
    },
    "status": {
      "type": "string",
      "enum": [
        "PENDING",
        "APPROVED",
        "DECLINED",
        "EXPIRED"
      ],
      "title": "Status"
    },
    "email": {
      "type": "string",
      "title": "Email"
    },
    "agentName": {
      "type": "string",
      "title": "Agentname"
    },
    "agentEmail": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentemail"
    },
    "tokenId": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenid"
    },
    "tokenIds": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Tokenids"
    },
    "requestedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Requestedscopes"
    },
    "grantedScopes": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Grantedscopes"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "expiresAt": {
      "type": "string",
      "title": "Expiresat"
    },
    "approvedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Approvedat"
    },
    "temporary": {
      "type": "boolean",
      "title": "Temporary",
      "description": "Temporary token: stops on its own validForHours after approval"
    },
    "validForHours": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Validforhours"
    },
    "tokenExpiresAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenexpiresat",
      "description": "Once active: when a temporary token stops"
    },
    "apiKey": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Apikey",
      "description": "The token, shown once at creation. Inert (403 token_pending_approval) until the person approves."
    },
    "tokenStatus": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "PENDING",
            "ACTIVE",
            "REVOKED"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Tokenstatus"
    },
    "pollSecret": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollsecret",
      "description": "Shown once"
    },
    "pollUrl": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Pollurl",
      "description": "Shown once; poll it every 30 to 60 s"
    }
  },
  "type": "object",
  "required": [
    "id",
    "action",
    "status",
    "email",
    "agentName",
    "tokenId",
    "tokenIds",
    "requestedScopes",
    "grantedScopes",
    "createdAt",
    "expiresAt",
    "approvedAt",
    "temporary",
    "validForHours",
    "tokenExpiresAt"
  ],
  "title": "AccessRequestView"
}

Account

GET/api/v1/mescope: any active token

Who am I. The person, token and call settings behind the bearer.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/me" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "user": {
      "additionalProperties": true,
      "type": "object",
      "title": "User",
      "description": "{id, email, name}: the person the token acts for"
    },
    "key": {
      "additionalProperties": true,
      "type": "object",
      "title": "Key",
      "description": "{id, name, agentName, agentEmail, prefix, scopes, engines, outboundOnly, createdAt, expiresAt, lastUsedAt}: engines are the voiceModel values this token may use"
    },
    "settings": {
      "additionalProperties": true,
      "type": "object",
      "title": "Settings",
      "description": "{notifyOnCall, allowedHours, allowedHoursText}"
    }
  },
  "type": "object",
  "required": [
    "user",
    "key",
    "settings"
  ],
  "title": "MeView"
}

Calls

POST/api/v1/callsscope: calls:book_approved | calls:book_new

Book a call (call_book). MCP tool call_book. Books an outbound call; the voice agent dials within seconds, or at scheduledAt (up to 30 days ahead; you are woken wakeLeadSeconds before by the call.wake webhook, and the response's wakeAt and wakePrompt are for your own cron; requireSeat holds the dial until you call_wait it). call_hangup cancels a scheduled call. Within your approved permissions it dials: no per-call approval. calls:book_new for any number (it becomes an approved contact), calls:book_approved for numbers already on the person's approved list. The briefing has two sides: details (shareable) and history (private, never voiced); playbooks say how to handle the call (intent, never lines). Refused with 409 outside_allowed_hours outside the hours the person set; at most 5 bookings per hour per key. The response carries callLink, the live call page; paste it to your human.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"recipient": "+12565550123", "who": "Dryden, adjuster at Allstate", "goal": "Ask for the status of the Mavrin demand letter sent on June 18", "facts": ["Claim 051518502", "Demand sent June 18 by email"], "mayShare": ["claim number", "date the demand was sent"], "neverShare": ["amounts not in the demand"], "successCriteria": "We know who has the file and when they will answer", "maxMinutes": 10, "idempotencyKey": "mavrin-status-2026-11-02"}'
Request body schema
{
  "properties": {
    "recipient": {
      "type": "string",
      "title": "Recipient",
      "description": "Who to call, E.164 (+12565550123). Needs calls:book_new unless the number is an approved contact (calls:book_approved)."
    },
    "who": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Who",
      "description": "Who the recipient is, e.g. 'Dryden, adjuster at Allstate'"
    },
    "goal": {
      "type": "string",
      "maxLength": 1000,
      "minLength": 1,
      "title": "Goal",
      "description": "What the call is for, in one or two sentences"
    },
    "details": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 30,
      "title": "Details",
      "description": "Shareable details: the information the agent is calling with (claim number, prescriptions and quantities, addresses). On a call for the person themself it says what is about them (name, numbers, address, date of birth) only when the other side needs it; leave out what the call does not need, such as their name for a stock check, and add no callback number unless the goal needs a callback"
    },
    "history": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 20,
      "title": "History",
      "description": "Private history for the agent's judgment only, never voiced or hinted at (\"they pushed back on X last time; if they do, offer Y\"). Acted on only once the other side raises the subject"
    },
    "playbooks": {
      "items": {
        "type": "string",
        "maxLength": 1500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 5,
      "title": "Playbooks",
      "description": "Private how-to-handle-this-call guidance (tone, approach). Describe the intent; never give lines to say, the agent copies them word for word"
    },
    "smallTalk": {
      "items": {
        "type": "string",
        "maxLength": 200,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 6,
      "title": "Smalltalk",
      "description": "Small-talk topics for lulls: ids from GET /api/v1/small-talk/topics or your own topic. Empty: a few are picked to fit the day"
    },
    "facts": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 30,
      "title": "Facts",
      "description": "Same as details (kept for older clients)"
    },
    "mayShare": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 30,
      "title": "Mayshare",
      "description": "Same as details (kept for older clients)"
    },
    "neverShare": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 30,
      "title": "Nevershare",
      "description": "Hard limits: things it must never say, whatever is asked"
    },
    "collect": {
      "anyOf": [
        {
          "additionalProperties": {
            "type": "string",
            "maxLength": 300
          },
          "propertyNames": {
            "maxLength": 60,
            "minLength": 1
          },
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "title": "Collect",
      "description": "What to find out: field name -> what to ask (at most 20). The transcript carries the answers; AI Call does not summarize."
    },
    "successCriteria": {
      "type": "string",
      "maxLength": 1000,
      "title": "Successcriteria",
      "description": "When the call has done its job",
      "default": ""
    },
    "openingLine": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ],
      "title": "Openingline",
      "description": "First sentence the agent says (optional)"
    },
    "goalMode": {
      "type": "boolean",
      "title": "Goalmode",
      "description": "On: the goal is the outcome the call must produce, and the agent pushes toward it tactfully with everything it has (rapport, what stands between them and yes, objections answered from the file or a lookup, trial closes, locking in amount/date/method/who). Off: it collects what the goal asks for without pushing for commitments",
      "default": false
    },
    "agentName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 40,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentname",
      "description": "The name the phone agent goes by on this call: the name you, the assistant booking it, go by (never a model or product name such as Claude, GPT or Codex). Default: your own persona's name (call_persona_save), else your token's name if it is a usable first name. The voice is matched to the name (a woman's name never gets a man's voice)"
    },
    "agentRole": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentrole",
      "description": "Who it is on this call, e.g. 'the office coordinator at Northside Plumbing'; with it the agent speaks as that team"
    },
    "company": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Company",
      "description": "The business this call is for; with it the agent speaks as that team and never names the person. Nothing is fixed on the account: you say it per call, or a saved profile piece does"
    },
    "rules": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 15,
      "title": "Rules",
      "description": "How to be on this kind of call: the house rules of a side of life (a profile sets them: errands with the public, friends, family, a business), e.g. what stays private, how familiar to be, what it never promises"
    },
    "persona": {
      "type": "string",
      "maxLength": 3000,
      "title": "Persona",
      "description": "Who your phone agent is as a person: a few sentences of background, quirks, how they talk, a bit of personal history and local ties (see call_guide for a sample to adapt). It plays the character, never recites it; if asked whether it is an AI it still says yes",
      "default": ""
    },
    "scene": {
      "type": "string",
      "maxLength": 1500,
      "title": "Scene",
      "description": "The vibe and local color for this call: where and when it is, what people around there are talking about, the mood to set, how to connect before getting to business (see call_guide)",
      "default": ""
    },
    "vibe": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Vibe",
      "description": "How it should come across: 'energetic and kind', 'compassionate and upbeat', 'relaxed and chill', 'persistent and kind', 'dismissive and forward'"
    },
    "voiceModel": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "grok",
            "qwen",
            "gpt"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Voicemodel",
      "description": "The model that is the voice on the phone: grok (default), qwen (Qwen 3.8 Omni: replies start a little later, and it needs more steering from the seat) or gpt (GPT-Live 1: full duplex, its tools run on a backend model). Else your persona's, else the account's"
    },
    "voice": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^(?:[a-z][a-z0-9_-]{1,30}|voice_[A-Za-z0-9]{1,64})$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Voice",
      "description": "xAI voice id. Male: altair, atlas, castor, cosmo, helios, helix, kepler, leo, lumen, lux, naksh, orion, perseus, rex, rigel, sal, sirius, zagan, zenith. Female: ara, aurora, carina, celeste, eve, iris, liora, luna, ursa. On GPT-Live also its own voices (marin, cedar...) or the id of one of the account's own (voice_...). Default: the account's voice"
    },
    "helpPolicy": {
      "type": "string",
      "enum": [
        "both",
        "seat_first",
        "seat_only"
      ],
      "title": "Helppolicy",
      "description": "Who gets the phone agent's ask_for_help questions: both (default: you through call_wait/call_answer and the person by text, at the same moment; the first answer wins and the other side is told), seat_first (the person is texted only if you have not answered within ownerFallbackSeconds), seat_only (never text the person)",
      "default": "both"
    },
    "ownerFallbackSeconds": {
      "type": "integer",
      "maximum": 120.0,
      "minimum": 5.0,
      "title": "Ownerfallbackseconds",
      "description": "seat_first: seconds before the person is texted the question",
      "default": 25
    },
    "maxMinutes": {
      "type": "integer",
      "maximum": 120.0,
      "minimum": 1.0,
      "title": "Maxminutes",
      "description": "Hard limit for the call length in minutes (1 to 120, default 15)",
      "default": 15
    },
    "scheduledAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Scheduledat",
      "description": "Book it for later: when to dial, ISO 8601 with a UTC offset (2026-11-02T15:00:00-06:00 or ...Z), 1 minute to 30 days ahead, inside the person's allowed hours. Leave out to dial now"
    },
    "wakeLeadSeconds": {
      "type": "integer",
      "maximum": 3600.0,
      "minimum": 30.0,
      "title": "Wakeleadseconds",
      "description": "Scheduled calls: how long before scheduledAt to wake you (the call.wake webhook and wakeAt in the response, for your own cron)",
      "default": 120
    },
    "requireSeat": {
      "type": "boolean",
      "title": "Requireseat",
      "description": "Scheduled calls: dial only once you are in the seat (you called call_wait for this booking after wakeAt). Not there within seatGraceMinutes: the call is cancelled and the person is told",
      "default": false
    },
    "seatGraceMinutes": {
      "type": "integer",
      "maximum": 60.0,
      "minimum": 1.0,
      "title": "Seatgraceminutes",
      "description": "requireSeat: how long past scheduledAt to wait for you",
      "default": 10
    },
    "context": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "business",
            "personal"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Context",
      "description": "personal: a call for the person themself (checking on a friend, family, an errand): with a business it asks like any customer and names the person, or gives anything about them, only when the call needs it (a pharmacy looking up their prescription); a friend or family member hears who it is from. business: for a company; it speaks as the team. Default: business when the booking gives a company or agentRole, else on the person's behalf"
    },
    "vault": {
      "items": {
        "type": "string",
        "maxLength": 60,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 20,
      "title": "Vault",
      "description": "Items from the person's vault this call may use, by name (alt ID; call_vault lists them). The voice agent gets a value only when it needs it, after the person approves when the item says ask first; you never see the values, and records show **ALT_ID:<name>** in place of any value the person keeps out of transcripts"
    },
    "payment": {
      "anyOf": [
        {
          "properties": {
            "allowed": {
              "type": "boolean",
              "title": "Allowed",
              "description": "The agent may pay by card on this call",
              "default": true
            },
            "maxAmount": {
              "anyOf": [
                {
                  "type": "number",
                  "maximum": 100000.0,
                  "exclusiveMinimum": 0.0
                },
                {
                  "type": "null"
                }
              ],
              "title": "Maxamount",
              "description": "Most it may pay (in dollars); a higher total is checked with you first"
            },
            "approve": {
              "type": "string",
              "enum": [
                "text",
                "none"
              ],
              "title": "Approve",
              "description": "text (default): before any card is sent the person gets \"Total to pay is $X at <who> - Card ending in 1111. Approve?\" with the items; YES releases the card, anything else goes back to the agent, which rechecks the order with them. none: no approval step",
              "default": "text"
            },
            "cardLast4": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^[0-9]{4}$"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Cardlast4",
              "description": "The card's last four digits, for the approval text (never the full number)"
            },
            "card": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^[a-z0-9][a-z0-9-]{0,39}$"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Card",
              "description": "Which of the person's stored cards to use, by its vault name (e.g. amex). Only the name ever reaches AI Call; it is echoed in the payment event so the seat sends the right one"
            },
            "vaultCard": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 60,
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Vaultcard",
              "description": "A card in the person's AI Call vault, by name (alt ID; call_vault lists cards with kind card). When they are ready to take payment (and the person approved the total, when totals are approved) AI Call hands it straight to the voice agent: nobody sends a card"
            }
          },
          "type": "object",
          "title": "Payment",
          "description": "Paying by card over the phone (Sam, 2026-10-26: \"places the order, and can pay over the phone ... I wouldn't send\nthe card information until they get to the point of needing it\")."
        },
        {
          "type": "null"
        }
      ],
      "description": "Let it pay by card over the phone: {allowed, maxAmount}. When they are ready to take payment it asks for the card (a payment event, the call.payment webhook, a text to the person); send it with call_card then, never before"
    },
    "handoff": {
      "anyOf": [
        {
          "properties": {
            "to": {
              "type": "string",
              "title": "To",
              "description": "Who takes the call: \"owner\" (the person, on their phone) or an E.164 number on their approved list",
              "default": "owner"
            },
            "when": {
              "type": "string",
              "enum": [
                "reached",
                "live_person"
              ],
              "title": "When",
              "description": "reached: the agent gets the person named in reach on the line (front desk, phone menu), tells them someone is free to take it, then hands it over. live_person: the agent says nothing while it holds and works the phone menu; the moment a live person answers it says one moment and hands it over",
              "default": "reached"
            },
            "reach": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200,
                  "minLength": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Reach",
              "description": "reached: who to get on the line, e.g. 'John at City Lumber' (default: who)"
            },
            "mode": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "warm",
                    "cold"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "title": "Mode",
              "description": "warm: whoever takes it is briefed privately first; cold: connected at once. Default warm for reached, cold for live_person (a live agent on a hold line will not wait)"
            }
          },
          "type": "object",
          "title": "Handoff",
          "description": "Pickup transfer (Sam, 2026-10-26): \"Get me on the phone with John at City Lumber\" (reach) or \"wait on hold with\nthe IRS and give the call to me as soon as they answer\" (hold)."
        },
        {
          "type": "null"
        }
      ],
      "description": "Pickup transfer: the call exists to get someone on the line for the person, then hand it to them. {to, when reached|live_person, reach, mode warm|cold}. Needs calls in LiveKit rooms"
    },
    "voicemail": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "message",
            "hangup",
            "transfer"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Voicemail",
      "description": "If it reaches voicemail: message (it waits for the tone and leaves a short message: who, what it is about, the callback number; then hangs up), hangup, or transfer (it rings the person, or the handoff target, so they leave the message themself; it says nothing). Default: transfer for a handoff call, message for a call for a business, hangup for a call on the person's own behalf (it leaves neither their name nor a number behind unless you ask for a message)"
    },
    "callbackNumber": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Callbacknumber",
      "description": "The number a voicemail message asks them to call back (default: the number the call comes from)"
    },
    "seatBrief": {
      "type": "string",
      "maxLength": 4000,
      "title": "Seatbrief",
      "description": "A note to your future self for when you wake (why this call, what to watch for, what to do after). Never told to the voice agent; returned by call_upcoming and the call.wake webhook",
      "default": ""
    },
    "rolePlay": {
      "type": "boolean",
      "title": "Roleplay",
      "description": "A practice call: someone plays the other side. Its caller memory is kept apart: it builds on earlier practice calls only, and never shows up in (or reads from) the memory of real calls. Default false: every call is real, calls to the person's own phone included",
      "default": false
    },
    "idempotencyKey": {
      "type": "string",
      "pattern": "^[A-Za-z0-9._:-]{8,200}$",
      "title": "Idempotencykey",
      "description": "Required. Same key + same body returns the first booking; never dials twice. 8 to 200 of A-Z a-z 0-9 . _ : -"
    }
  },
  "type": "object",
  "required": [
    "recipient",
    "who",
    "goal",
    "idempotencyKey"
  ],
  "title": "CallBookRequest",
  "description": "call_book: book an outbound call. The briefing is what the phone agent knows; it is frozen at dial time."
}
Response schema (201)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid",
      "description": "Use it with every other call tool"
    },
    "state": {
      "type": "string",
      "enum": [
        "SCHEDULED",
        "QUEUED",
        "DIALING",
        "RINGING",
        "CONNECTED",
        "WRAPPING",
        "ENDED",
        "FAILED",
        "CANCELLED",
        "UNCERTAIN"
      ],
      "title": "State"
    },
    "recipient": {
      "type": "string",
      "title": "Recipient"
    },
    "who": {
      "type": "string",
      "title": "Who"
    },
    "maxMinutes": {
      "type": "integer",
      "title": "Maxminutes"
    },
    "callLink": {
      "type": "string",
      "title": "Calllink",
      "description": "https://aicall.prodevgroup.com/call/<token>: live transcript, notes, transfer, hang up, listen; read-only for 30 days after the call"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "note": {
      "type": "string",
      "title": "Note",
      "description": "Plain words for the human, e.g. \"Dialing now.\""
    },
    "scheduledAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Scheduledat",
      "description": "When it dials (scheduled calls)"
    },
    "wakeAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeat",
      "description": "When to be back in the seat: set your own cron or wake-up for this time (the call.wake webhook fires then too)"
    },
    "requireSeat": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Requireseat"
    },
    "wakePrompt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeprompt",
      "description": "A ready-made prompt for your own scheduled wake-up (cron, scheduled task)"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "state",
    "recipient",
    "who",
    "maxMinutes",
    "callLink",
    "createdAt",
    "note"
  ],
  "title": "CallBooking"
}
POST/api/v1/calls/from-templatescope: calls:book_approved | calls:book_new

Book a call from a template (call_from_template). MCP tool call_from_template. Fills a template's blanks from fill, lays booking (recipient, idempotencyKey and any call_book field you want to change) on top, and books it exactly like call_book. A missing required blank answers 400 naming it.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/from-template" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"booking": {}}'
Request body schema
{
  "properties": {
    "template": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 48,
          "minLength": 2
        },
        {
          "type": "null"
        }
      ],
      "title": "Template",
      "description": "One template name from call_templates"
    },
    "use": {
      "anyOf": [
        {
          "items": {
            "type": "string",
            "maxLength": 48,
            "minLength": 2
          },
          "type": "array",
          "maxItems": 6
        },
        {
          "type": "null"
        }
      ],
      "title": "Use",
      "description": "Several templates mashed together, in order (lists add up, objects merge, later single values win): e.g. a profile, a persona, a scene and a kind of call"
    },
    "fill": {
      "additionalProperties": true,
      "type": "object",
      "title": "Fill",
      "description": "Blank name to value (a list for list blanks)"
    },
    "booking": {
      "additionalProperties": true,
      "type": "object",
      "title": "Booking",
      "description": "call_book fields on top: recipient and idempotencyKey at least; any other field overrides the template"
    }
  },
  "type": "object",
  "required": [
    "booking"
  ],
  "title": "FromTemplate"
}
Response schema (201)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid",
      "description": "Use it with every other call tool"
    },
    "state": {
      "type": "string",
      "enum": [
        "SCHEDULED",
        "QUEUED",
        "DIALING",
        "RINGING",
        "CONNECTED",
        "WRAPPING",
        "ENDED",
        "FAILED",
        "CANCELLED",
        "UNCERTAIN"
      ],
      "title": "State"
    },
    "recipient": {
      "type": "string",
      "title": "Recipient"
    },
    "who": {
      "type": "string",
      "title": "Who"
    },
    "maxMinutes": {
      "type": "integer",
      "title": "Maxminutes"
    },
    "callLink": {
      "type": "string",
      "title": "Calllink",
      "description": "https://aicall.prodevgroup.com/call/<token>: live transcript, notes, transfer, hang up, listen; read-only for 30 days after the call"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "note": {
      "type": "string",
      "title": "Note",
      "description": "Plain words for the human, e.g. \"Dialing now.\""
    },
    "scheduledAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Scheduledat",
      "description": "When it dials (scheduled calls)"
    },
    "wakeAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeat",
      "description": "When to be back in the seat: set your own cron or wake-up for this time (the call.wake webhook fires then too)"
    },
    "requireSeat": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Requireseat"
    },
    "wakePrompt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeprompt",
      "description": "A ready-made prompt for your own scheduled wake-up (cron, scheduled task)"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "state",
    "recipient",
    "who",
    "maxMinutes",
    "callLink",
    "createdAt",
    "note"
  ],
  "title": "CallBooking"
}
GET/api/v1/call-guidescope: none

How to set up a great call (call_guide). MCP tool call_guide. Read once: what makes a great call (a complete persona, clear objectives, real connection and local color), samples to adapt, and how to save the pieces with {blanks} so the next call only fills them in.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/call-guide" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/templatesscope: any active token

Call templates (call_templates). MCP tool call_templates. The built-in starters and the person's own saved templates (a saved one wins over a built-in of the same name): name, description, fields (the blanks), origin.

Parameters

nameintypedescription
kindquerystringOnly this kind

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/templates" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/templates/{name}scope: any active token

One template (call_template). MCP tool call_template. The whole template: its blanks and the call_book body it fills in.

Parameters

nameintypedescription
namepathrequiredstring

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/templates/<name>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
PUT/api/v1/templates/{name}scope: calls:book_approved | calls:book_new

Save a template (call_template_save). MCP tool call_template_save. Saves (or replaces) one of the person's templates, for all their agents: {description, fields: [{name, about, required}], booking: call_book fields whose strings may hold {blanks}}. fields left out: every blank is required. Saving a built-in's name customizes it for this person.

Parameters

nameintypedescription
namepathrequiredstring

Example

curl -X PUT "https://aicall.prodevgroup.com/api/v1/templates/<name>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
DELETE/api/v1/templates/{name}scope: calls:book_approved | calls:book_new

Delete a saved template (call_template_delete). MCP tool call_template_delete. Deletes one of the person's saved templates (a built-in of the same name comes back).

Parameters

nameintypedescription
namepathrequiredstring

Example

curl -X DELETE "https://aicall.prodevgroup.com/api/v1/templates/<name>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/storiesscope: calls:listen

Call stories (call_story). MCP tool call_story. The person's call stories: one per number and direction (out: calls to that number, in: calls from it), each how the calls that went well with that place go, what made them work and the lessons learned. With number: that number's stories in full ({number, stories: [{direction, place, story, chars, updatedAt, updatedBy}]}; 404 when there is none); without: the list, without the text. The voice agent gets the story up front on every call out to that number, or in from it, and when it is dialed into a call.

Parameters

nameintypedescription
numberquerystringE.164 phone number, e.g. +12565550123 (in a URL write the + as %2B)
directionquerystringout: the calls AI Call makes to that number; in: the calls that come in from it (default: both)

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/stories" \
  -H "Authorization: Bearer $AICALL_TOKEN"
PUT/api/v1/storiesscope: calls:book_approved | calls:book_new

Save a call story (call_story_save). MCP tool call_story_save. Saves the story for one number and direction, replacing the one before (kept in the audit log): {number, direction out|in, place, story}. Write it after a call that went well or taught something, whole: who they are and how they answer, how the call goes step by step, how to say each thing, what went wrong before and how to prevent it, what to check before hanging up. At most 12000 characters: the voice agent reads all of it on every call.

Example

curl -X PUT "https://aicall.prodevgroup.com/api/v1/stories" \
  -H "Authorization: Bearer $AICALL_TOKEN"
DELETE/api/v1/storiesscope: calls:book_approved | calls:book_new

Delete a call story (call_story_save remove). MCP tool call_story_save. With remove: deletes the story for one number and direction (kept in the audit log).

Parameters

nameintypedescription
numberqueryrequiredstringE.164 phone number, e.g. +12565550123 (in a URL write the + as %2B)
directionqueryrequiredstringout: the calls AI Call makes to that number; in: the calls that come in from it

Example

curl -X DELETE "https://aicall.prodevgroup.com/api/v1/stories?number=<number>&direction=<direction>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/recordsscope: calls:listen

Place records (call_records). MCP tool call_records. What the person ordered, asked for, booked or paid with each place, and how it came out, newest first: {records: [{recordId, number, place, direction, kind, at, summary, items, total, result, bookingId, source, updatedAt}]}. AI Call writes one by itself a few seconds after each real call (source call); agents add or fix them (source agent). With number: that place's; kind narrows it; q: every word must appear (an item, a place, a result). The voice agent gets a place's latest records on every call there, under its story.

Parameters

nameintypedescription
numberquerystringE.164 phone number, e.g. +12565550123 (in a URL write the + as %2B). Only that place's records
kindquerystringorder, lookup (stock, price, hours, a prescription or order status), booking, payment, issue (a complaint, refund, credit, something missing or wrong) or other
qquerystringWords that must all appear, e.g. Cajun, Zelda, refill
limitqueryinteger

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/records" \
  -H "Authorization: Bearer $AICALL_TOKEN"
PUT/api/v1/recordsscope: calls:book_approved | calls:book_new

Add or fix a place record (call_record_save). MCP tool call_record_save. Adds a record ({number, kind, summary, items, total, result, at, place, direction, bookingId}), or with recordId changes the fields given on one (what the call could not know: the receipt's prices, the real total, what happened after). The record before is kept in the audit log.

Example

curl -X PUT "https://aicall.prodevgroup.com/api/v1/records" \
  -H "Authorization: Bearer $AICALL_TOKEN"
DELETE/api/v1/records/{recordId}scope: calls:book_approved | calls:book_new

Delete a place record (call_record_save remove). MCP tool call_record_save. With remove: deletes one record (kept in the audit log).

Parameters

nameintypedescription
recordIdpathrequiredstring

Example

curl -X DELETE "https://aicall.prodevgroup.com/api/v1/records/<recordId>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/vaultscope: any active token

The person's vault items, by name (call_vault). MCP tool call_vault. The details the person keeps in their vault, by name (alt ID), with askFirst and showInTranscript: {items, note}. Never the values. Name the ones a call may use in its booking's vault list; the voice agent gets a value only when it needs it on that call.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/vault" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/vault/readscope: calls:vault

Read one vault item (call_vault_read). MCP tool call_vault_read. The value of one item in the person's vault, for you (the seat) only: {status, altId, kind, value | card | fileName..., use}. Needs calls:vault. An ask-first item opens only during a live call (bookingId), once the person approves it there: until then {status: awaiting_approval}. Give the voice agent a value with {{vault:<name>}} in call_answer or call_note.

Parameters

nameintypedescription
namequeryrequiredstring
bookingIdquerystring

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/vault/read?name=<name>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/personascope: any active token

Your own persona (call_persona). MCP tool call_persona. The character you, this agent, play on the calls you book: {persona: {name, persona, vibe, voice} or null, nameOnCalls}. Each agent has its own.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/persona" \
  -H "Authorization: Bearer $AICALL_TOKEN"
PUT/api/v1/personascope: any active token

Save your own persona (call_persona_save). MCP tool call_persona_save. Saves the character you play on your calls: {name (the first name you go by; never a model name, never anything labelled assistant), persona (background, quirks, how you talk, a bit of history, local ties), vibe, voice (else matched to the name)}. Replaces your previous one; update it as you learn what works.

Example

curl -X PUT "https://aicall.prodevgroup.com/api/v1/persona" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/changelogscope: any active token

Changes to the person's profile and their agents' personas (call_changelog). MCP tool call_changelog. Newest first: {items: [{id, at, kind: profile | persona, agent (whose persona: tokenId, name), by {type: agent | operator, name, tokenId?}, changes: [{field, from, to}]}]}. Every change made through call_profile_update, call_persona_save or the server's command line is here. The person's own numbers show only their last four digits.

Parameters

nameintypedescription
kindquerystringonly profile or only persona changes
tokenIdquerystringonly this agent's persona
limitqueryinteger

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/changelog" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/profilescope: any active token

The person's call profile (call_profile). MCP tool call_profile. company, the business persona (role, team, opener, vibe), home (place, lat, lon, timezone: the weather small talk), teams and feeds (small talk), voice, recording; and whether a phone and text line are on file.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/profile" \
  -H "Authorization: Bearer $AICALL_TOKEN"
PATCH/api/v1/profilescope: calls:book_approved | calls:book_new

Update the person's call profile (call_profile_update). MCP tool call_profile_update. Changes only the settings you send (null removes one; persona and home merge key by key). Never the person's phone or text line: they set those themselves. Audited.

Example

curl -X PATCH "https://aicall.prodevgroup.com/api/v1/profile" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/calls/{bookingId}/eventsscope: any active token (the one that booked)

Wait for call events (call_wait). MCP tool call_wait. Long-polls up to waitMs (max 15000) and returns the events after your cursor: state, transcript (both sides; settled lines by default, view=all adds the provisional ones), agent_state, note, question (answer it with call_answer), text, control. A late joiner replays from after=0. The last call_wait after the call ends carries result.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book
afterqueryintegerReturn events with seq greater than this (0 = from the start)
waitMsqueryintegerHow long to wait for new events, 0 to 15000 ms
viewquerystringsettled (default): transcript lines once final; all: provisional lines too

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/events" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid"
    },
    "events": {
      "items": {
        "properties": {
          "seq": {
            "type": "integer",
            "title": "Seq",
            "description": "Durable, increasing; pass the last one back as after"
          },
          "at": {
            "type": "string",
            "title": "At",
            "description": "When AI Call recorded it (ISO 8601)"
          },
          "sourceAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sourceat"
          },
          "callId": {
            "type": "string",
            "title": "Callid"
          },
          "bookingId": {
            "type": "string",
            "title": "Bookingid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "state",
              "transcript",
              "agent_state",
              "note",
              "question",
              "text",
              "control",
              "proposal",
              "relay",
              "assistant",
              "payment",
              "vault"
            ],
            "title": "Kind"
          },
          "data": {
            "additionalProperties": true,
            "type": "object",
            "title": "Data",
            "description": "state: {state, outcome?}; transcript: {speaker caller|agent|owner|transfer:<number>, itemId, text, final, settled}; agent_state: {state listening|speaking}; note: {noteId, source, author, kind, mode, text, state}; question: {questionId, text, state OPEN|ANSWERED|TIMED_OUT, answer?, answeredBy?} (answer it with call_answer); text: {direction out|in, address, body, state}; control: {action, mode, actor}; proposal: {proposalId, to, body, state PENDING|SENT|DECLINED|EXPIRED|FAILED|REVISE} (the agent asked to text them; the person approves)"
          }
        },
        "type": "object",
        "required": [
          "seq",
          "at",
          "callId",
          "bookingId",
          "kind",
          "data"
        ],
        "title": "CallEvent"
      },
      "type": "array",
      "title": "Events"
    },
    "cursor": {
      "type": "integer",
      "title": "Cursor",
      "description": "Pass back as after"
    },
    "state": {
      "type": "string",
      "enum": [
        "SCHEDULED",
        "QUEUED",
        "DIALING",
        "RINGING",
        "CONNECTED",
        "WRAPPING",
        "ENDED",
        "FAILED",
        "CANCELLED",
        "UNCERTAIN"
      ],
      "title": "State"
    },
    "more": {
      "type": "boolean",
      "title": "More",
      "description": "true: more events are ready, call again at once"
    },
    "callLink": {
      "type": "string",
      "title": "Calllink"
    },
    "caller": {
      "anyOf": [
        {
          "properties": {
            "number": {
              "type": "string",
              "title": "Number",
              "description": "The caller's number: pass it to call_memory"
            },
            "name": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Name",
              "description": "Their name when AI Call knows it (the person themself, a saved contact or caller memory)"
            }
          },
          "type": "object",
          "required": [
            "number"
          ],
          "title": "CallerView"
        },
        {
          "type": "null"
        }
      ],
      "description": "Who called in (only on a call that came in): read their memory with call_memory"
    },
    "voiceModel": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "grok",
            "qwen",
            "gpt"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Voicemodel",
      "description": "The model that is the voice on this call, once it is on: qwen needs more nudging, gpt hears your notes while it talks (call guide section 5)"
    },
    "result": {
      "anyOf": [
        {
          "properties": {
            "bookingId": {
              "type": "string",
              "title": "Bookingid"
            },
            "state": {
              "type": "string",
              "enum": [
                "SCHEDULED",
                "QUEUED",
                "DIALING",
                "RINGING",
                "CONNECTED",
                "WRAPPING",
                "ENDED",
                "FAILED",
                "CANCELLED",
                "UNCERTAIN"
              ],
              "title": "State"
            },
            "outcome": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "answered",
                    "no_answer",
                    "busy",
                    "failed",
                    "canceled"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "title": "Outcome"
            },
            "hungUpBy": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "caller",
                    "agent",
                    "coach",
                    "owner",
                    "timeout",
                    "unknown"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "title": "Hungupby"
            },
            "recipient": {
              "type": "string",
              "title": "Recipient"
            },
            "who": {
              "type": "string",
              "title": "Who",
              "default": ""
            },
            "callerId": {
              "type": "string",
              "title": "Callerid",
              "default": ""
            },
            "startedAt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Startedat"
            },
            "answeredAt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Answeredat"
            },
            "endedAt": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Endedat"
            },
            "durationSeconds": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Durationseconds"
            },
            "notes": {
              "items": {
                "$ref": "#/components/schemas/NoteView"
              },
              "type": "array",
              "title": "Notes"
            },
            "questions": {
              "items": {
                "$ref": "#/components/schemas/QuestionView"
              },
              "type": "array",
              "title": "Questions"
            },
            "passedAlong": {
              "items": {
                "$ref": "#/components/schemas/PassedAlongView"
              },
              "type": "array",
              "title": "Passedalong",
              "description": "Instructions and feedback the agent passed to the team during the call (relay events)"
            },
            "assistant": {
              "items": {
                "$ref": "#/components/schemas/AssistantLineView"
              },
              "type": "array",
              "title": "Assistant",
              "description": "The seat's own lines (call_assistant, call_text_owner): what it did and found, never spoken"
            },
            "texts": {
              "items": {
                "$ref": "#/components/schemas/TextView"
              },
              "type": "array",
              "title": "Texts"
            },
            "proposals": {
              "items": {
                "additionalProperties": true,
                "type": "object"
              },
              "type": "array",
              "title": "Proposals",
              "description": "Texts the agent asked to send to the other side: {proposalId, to, body, state SENT|DECLINED|EXPIRED|FAILED, decidedBy}"
            },
            "transcript": {
              "items": {
                "$ref": "#/components/schemas/TranscriptLine"
              },
              "type": "array",
              "title": "Transcript"
            },
            "callLink": {
              "type": "string",
              "title": "Calllink"
            },
            "recording": {
              "anyOf": [
                {
                  "additionalProperties": true,
                  "type": "object"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Recording",
              "description": "{url, seconds}: the call's audio (MP3) when the person records calls; kept 30 days. The url needs calls:listen"
            },
            "minutes": {
              "$ref": "#/components/schemas/Minutes",
              "description": "Billable minutes by leg {callee, transfer, agent}, each rounded up to a whole minute. All 0 for a call that was never answered, and until the call has ended"
            },
            "line": {
              "anyOf": [
                {
                  "additionalProperties": true,
                  "type": "object"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Line",
              "description": "{theirAudioLostPercent, choppy, note}: how much of the other side's audio reached AI Call; choppy at 5% or more (their phone line or the route to it, not the agent). Calls in LiveKit rooms only; null when not measured"
            }
          },
          "type": "object",
          "required": [
            "bookingId",
            "state",
            "outcome",
            "hungUpBy",
            "recipient",
            "startedAt",
            "answeredAt",
            "endedAt",
            "durationSeconds",
            "notes",
            "transcript",
            "callLink",
            "minutes"
          ],
          "title": "CallResult"
        },
        {
          "type": "null"
        }
      ],
      "description": "Present on the last call_wait after the call ended"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "events",
    "cursor",
    "state",
    "more",
    "callLink"
  ],
  "title": "CallEvents"
}
POST/api/v1/calls/{bookingId}/seat/awayscope: calls:coach

Step out of the seat for a moment (call_seat_away). MCP tool call_seat_away. For the seat on an incoming call that has to leave it briefly, for example to hand a task to another of your person's bots. AI Call wakes you again at once (reason back) instead of after a minute; when you are back, read the call from the start (call_wait after=0) before you carry on. Prefer staying on the call. 10 per call.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/seat/away" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "string"}'
Request body schema
{
  "properties": {
    "reason": {
      "type": "string",
      "maxLength": 300,
      "minLength": 1,
      "title": "Reason",
      "description": "What you are stepping out to do, e.g. handing the Carson claim to the Legal Bot (your next run reads it here)"
    }
  },
  "type": "object",
  "required": [
    "reason"
  ],
  "title": "CallSeatAwayRequest"
}
Response schema (202)
{
  "properties": {
    "state": {
      "type": "string",
      "const": "AWAY",
      "title": "State"
    },
    "wakeUp": {
      "type": "string",
      "const": "sent",
      "title": "Wakeup"
    },
    "next": {
      "type": "string",
      "title": "Next"
    }
  },
  "type": "object",
  "required": [
    "state",
    "wakeUp",
    "next"
  ],
  "title": "SeatAway"
}
POST/api/v1/calls/{bookingId}/notesscope: calls:coach

Send the phone agent a note (call_note). MCP tool call_note. guidance: worked in where it fits, never read aloud; speak: said at the next pause (after the other person finishes and the agent's audio has played out). kind detail = information it may share; kind private = guidance for its judgment, never voiced. Say what to change from now on, not a line to say (it copies lines word for word). HELD until the call is answered. The person's own notes outrank yours. 1 note per 2 seconds, 60 per call.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/notes" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "The adjuster'\''s supervisor is Karen; ask for her if he cannot answer.", "mode": "guidance", "commandId": "note-0001"}'
Request body schema
{
  "properties": {
    "text": {
      "type": "string",
      "maxLength": 1000,
      "minLength": 1,
      "title": "Text"
    },
    "mode": {
      "type": "string",
      "enum": [
        "guidance",
        "speak"
      ],
      "title": "Mode",
      "description": "guidance: worked in where it fits the conversation, never read aloud; speak: the agent says it at the next pause (after the other person finishes and its own audio has played out)",
      "default": "guidance"
    },
    "kind": {
      "type": "string",
      "enum": [
        "detail",
        "private"
      ],
      "title": "Kind",
      "description": "detail: information it may share; private: guidance for its judgment, never voiced. Mid-call notes should say what to change from now on, not a line to say",
      "default": "detail"
    },
    "supersedes": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,80}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Supersedes",
      "description": "noteId of an earlier note not yet delivered; it is dropped"
    },
    "commandId": {
      "type": "string",
      "pattern": "^[A-Za-z0-9._:-]{8,200}$",
      "title": "Commandid",
      "description": "Required. Idempotency key for this note (8 to 200 of A-Z a-z 0-9 . _ : -)"
    }
  },
  "type": "object",
  "required": [
    "text",
    "commandId"
  ],
  "title": "CallNoteRequest",
  "description": "call_note: a note to the phone agent during the call."
}
Response schema (202)
{
  "properties": {
    "noteId": {
      "type": "string",
      "title": "Noteid"
    },
    "state": {
      "type": "string",
      "enum": [
        "QUEUED",
        "HELD"
      ],
      "title": "State",
      "description": "HELD until the call is answered"
    },
    "mode": {
      "type": "string",
      "enum": [
        "guidance",
        "speak"
      ],
      "title": "Mode"
    },
    "queuedAt": {
      "type": "string",
      "title": "Queuedat"
    }
  },
  "type": "object",
  "required": [
    "noteId",
    "state",
    "mode",
    "queuedAt"
  ],
  "title": "NoteQueued"
}
POST/api/v1/calls/{bookingId}/assistantscope: calls:coach

Add your own line to the call's transcript (call_assistant). MCP tool call_assistant. Your line in the call's transcript, shown on the call page and in the email as yours and never spoken on the call. doing true: what you are doing now ("Looking up the drive from Madison to Decatur"); false: what you found ("You have a meeting Thursday at 11; the two sites are 45 minutes apart"). Up to ten minutes after the call ends; 200 per call.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/assistant" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "string"}'
Request body schema
{
  "properties": {
    "text": {
      "type": "string",
      "maxLength": 1000,
      "minLength": 1,
      "title": "Text"
    },
    "doing": {
      "type": "boolean",
      "title": "Doing",
      "description": "true: what you are doing now (\"Looking up the drive from Madison to Decatur\"); false: what you found",
      "default": false
    }
  },
  "type": "object",
  "required": [
    "text"
  ],
  "title": "CallAssistantRequest",
  "description": "call_assistant: the seat's own line in the call's transcript (never spoken)."
}
Response schema (202)
{
  "properties": {
    "state": {
      "type": "string",
      "const": "ADDED",
      "title": "State"
    },
    "at": {
      "type": "string",
      "title": "At"
    },
    "seq": {
      "type": "integer",
      "title": "Seq"
    }
  },
  "type": "object",
  "required": [
    "state",
    "at",
    "seq"
  ],
  "title": "AssistantLine"
}
POST/api/v1/calls/{bookingId}/text-ownerscope: calls:coach

Text the person what you found (call_text_owner). MCP tool call_text_owner. Texts your person (only them, from their text line, signed with your name) what you found during the call, and adds the same line to the transcript. Short and useful: what you found, not what you are about to do. One text every 5 seconds, 40 per call; 409 no_text_line when they have none.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/text-owner" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"body": "string"}'
Request body schema
{
  "properties": {
    "body": {
      "type": "string",
      "maxLength": 600,
      "minLength": 1,
      "title": "Body"
    }
  },
  "type": "object",
  "required": [
    "body"
  ],
  "title": "CallTextOwnerRequest",
  "description": "call_text_owner: a text to the person, from their text line."
}
Response schema (202)
{
  "properties": {
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "at": {
      "type": "string",
      "title": "At"
    }
  },
  "type": "object",
  "required": [
    "state",
    "at"
  ],
  "title": "OwnerText"
}
POST/api/v1/calls/{bookingId}/answersscope: calls:coach

Answer the phone agent's question (call_answer). MCP tool call_answer. Answers the phone agent's ask_for_help question (a question event in call_wait). Facts only: the agent relays it as something it just found, exactly as given; put instructions in call_note instead. The first answer wins (you, the person by text, or the person on the call page); the person is told when you answered.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/answers" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "string"}'
Request body schema
{
  "properties": {
    "text": {
      "type": "string",
      "maxLength": 1000,
      "minLength": 1,
      "title": "Text",
      "description": "The answer, plain; the agent relays it exactly"
    },
    "questionId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]{1,80}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Questionid",
      "description": "Default: the latest open question"
    }
  },
  "type": "object",
  "required": [
    "text"
  ],
  "title": "CallAnswerRequest",
  "description": "call_answer: answer the phone agent's ask_for_help question (the first answer wins: you, or the person by text or on the call page)."
}
Response schema (202)
{
  "properties": {
    "questionId": {
      "type": "string",
      "title": "Questionid"
    },
    "state": {
      "type": "string",
      "const": "ANSWERED",
      "title": "State"
    },
    "answeredBy": {
      "type": "string",
      "title": "Answeredby"
    },
    "noteId": {
      "type": "string",
      "title": "Noteid"
    }
  },
  "type": "object",
  "required": [
    "questionId",
    "state",
    "answeredBy",
    "noteId"
  ],
  "title": "AnswerAccepted"
}
POST/api/v1/calls/{bookingId}/transferscope: calls:transfer

Transfer the call (call_transfer). MCP tool call_transfer. target "owner" (the person's phone) or an E.164 number already on their approved list. soft (warm): the agent tells them to hold a moment, they hear hold music while the target rings, and when the target picks up the agent briefs them privately (who is on the line, what they want) before connecting; instant (cold): hold music at once, connected the moment the target answers. Then the agent steps out and the two talk (recorded, each side transcribed under its own label); if not, the agent comes back, promises a callback and carries on. Watch call_wait for control events (transfer DIALING, CONNECTED, NO_ANSWER, BUSY). Calls in LiveKit rooms only (409 transfer_unavailable on the direct bridge).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/transfer" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target": "owner", "mode": "soft", "commandId": "xfer-0001"}'
Request body schema
{
  "properties": {
    "target": {
      "type": "string",
      "title": "Target",
      "description": "\"owner\" (the person who approved your token) or an E.164 number"
    },
    "mode": {
      "type": "string",
      "enum": [
        "soft",
        "instant"
      ],
      "title": "Mode",
      "description": "soft (warm): the agent tells them to hold a moment (the hold line), hold music plays while the target rings, and when the target picks up the agent briefs them privately before connecting; instant (cold): hold music at once, connected on the answer. If nobody answers within 25 s the agent comes back, promises a callback and carries on",
      "default": "soft"
    },
    "holdLine": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 300,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Holdline",
      "description": "soft only; said word for word. Default: \"Hey, my boss is free now and will take over from here. Please hold.\" on a business call (the agent speaks as a persona), the person's first name instead of \"my boss\" on a personal one, \"someone from our team\" for a number other than the person's"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key; send one so a retry never transfers twice"
    }
  },
  "type": "object",
  "required": [
    "target"
  ],
  "title": "CallTransferRequest",
  "description": "call_transfer: hand the live call to the person (owner) or to a number."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
POST/api/v1/calls/{bookingId}/cardscope: calls:coach

Send the card for a payment (call_card). MCP tool call_card. Only when the voice agent asked for it (a payment event with state NEEDED in call_wait, or the call.payment webhook): the card it reads to the other side to pay. Held in memory for that payment only; never stored, logged, recorded or returned. The recording pauses and numbers are masked in the transcript while it is out. 409 no_payment_request otherwise.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/card" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"number": "string", "exp": "string", "cvc": "string"}'
Request body schema
{
  "properties": {
    "number": {
      "type": "string",
      "maxLength": 23,
      "minLength": 12,
      "title": "Number",
      "description": "Card number (spaces or dashes are fine)"
    },
    "exp": {
      "type": "string",
      "maxLength": 7,
      "minLength": 4,
      "title": "Exp",
      "description": "Expiration, MM/YY"
    },
    "cvc": {
      "type": "string",
      "pattern": "^[0-9]{3,4}$",
      "title": "Cvc",
      "description": "Security code"
    },
    "zip": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 10
        },
        {
          "type": "null"
        }
      ],
      "title": "Zip",
      "description": "Billing ZIP, if they ask"
    },
    "name": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ],
      "title": "Name",
      "description": "Name on the card, if they ask"
    }
  },
  "type": "object",
  "required": [
    "number",
    "exp",
    "cvc"
  ],
  "title": "CardRequest",
  "description": "call_card: the card for a payment the voice agent asked for. Held in memory for that payment only: never stored,\nlogged, recorded or shown again."
}
POST/api/v1/card-drop/{bookingId}/{token}scope: none: the link is the permission

Drop the card for one payment (single-use link). The cardDropUrl from a payment event or the call.payment webhook: POST the card ({number, exp, cvc, zip?, name?}) here, with no token, while the voice agent is waiting for it (10 minutes at most, one use). Meant for a script that reads the card from a vault, so no AI ever sees it. 404 once the link is used, expired or not the open request.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book
tokenpathrequiredstring

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/card-drop/<bookingId>/<token>" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"number": "string", "exp": "string", "cvc": "string"}'
Request body schema
{
  "properties": {
    "number": {
      "type": "string",
      "maxLength": 23,
      "minLength": 12,
      "title": "Number",
      "description": "Card number (spaces or dashes are fine)"
    },
    "exp": {
      "type": "string",
      "maxLength": 7,
      "minLength": 4,
      "title": "Exp",
      "description": "Expiration, MM/YY"
    },
    "cvc": {
      "type": "string",
      "pattern": "^[0-9]{3,4}$",
      "title": "Cvc",
      "description": "Security code"
    },
    "zip": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 10
        },
        {
          "type": "null"
        }
      ],
      "title": "Zip",
      "description": "Billing ZIP, if they ask"
    },
    "name": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ],
      "title": "Name",
      "description": "Name on the card, if they ask"
    }
  },
  "type": "object",
  "required": [
    "number",
    "exp",
    "cvc"
  ],
  "title": "CardRequest",
  "description": "call_card: the card for a payment the voice agent asked for. Held in memory for that payment only: never stored,\nlogged, recorded or shown again."
}
POST/api/v1/calls/{bookingId}/hangupscope: calls:hangup

End the call (call_hangup). MCP tool call_hangup. soft: the agent closes the call in a sentence or two and the line drops after at most 45 s; hard: drops now. A call not dialed yet is cancelled.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/hangup" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "soft", "reason": "We have what we need", "commandId": "bye-0001"}'
Request body schema
{
  "properties": {
    "mode": {
      "type": "string",
      "enum": [
        "soft",
        "hard"
      ],
      "title": "Mode",
      "description": "soft: the agent says goodbye in one sentence, the line drops after at most 45 s; hard: drop now",
      "default": "soft"
    },
    "reason": {
      "type": "string",
      "maxLength": 500,
      "minLength": 1,
      "title": "Reason",
      "description": "Why; kept with the call record"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key"
    }
  },
  "type": "object",
  "required": [
    "reason"
  ],
  "title": "CallHangupRequest",
  "description": "call_hangup: end the call."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
POST/api/v1/calls/mescope: calls:book_approved | calls:book_new

Ring the person for their own call (call_me). MCP tool call_me. For when your person says "give me a call": AI Call rings their phone, and from that call they reach whoever they want: dial (connected as soon as they pick up), their keypad (the number, then #), or call_dial from you. The whole call is recorded start to finish (their recording setting decides at the start; nothing pauses it) and transcribed live, each side under its own label: stay in call_wait and be their help in the background (look things up, add to the calendar, follow up), reaching them through your own channels or the call page, never on the line. The voice agent is on hand: agent muted puts it on the line from the start, listening; ** on their keypad mutes or unmutes it; *8 on their keypad or call_hold has it take over a hold (it says nothing until a live person is back, then tells them one moment and brings your person back, ringing their phone if they hung up); a text from your person to the line, or call_mute false, has it speak. callerId mine: the people they call see their own cell (once verified with the carrier; until then AI Call's number). The call to them always shows AI Call's number.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/me" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"idempotencyKey": "string"}'
Request body schema
{
  "properties": {
    "dial": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Dial",
      "description": "E.164 to connect them to as soon as they pick up (an approved contact, or calls:book_new). Leave out and they dial from their keypad (the number, then #), or you dial later with call_dial"
    },
    "who": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Who",
      "description": "Who they are calling, e.g. 'Home Depot pro desk' (for the call page and the transcript)"
    },
    "about": {
      "type": "string",
      "maxLength": 1000,
      "title": "About",
      "description": "What the call is about: what the voice agent works from if it is asked to help",
      "default": ""
    },
    "details": {
      "items": {
        "type": "string",
        "maxLength": 500,
        "minLength": 1
      },
      "type": "array",
      "maxItems": 30,
      "title": "Details",
      "description": "What the voice agent may share if it helps (account or claim numbers, a PIN for a phone menu)"
    },
    "agent": {
      "type": "string",
      "enum": [
        "off",
        "muted"
      ],
      "title": "Agent",
      "description": "The voice agent. off: not on the line until it is needed (** or *8 on their keypad, call_hold, call_mute false, or a text from them). muted: on the line from the start, listening and saying nothing until unmuted or messaged",
      "default": "off"
    },
    "callerId": {
      "type": "string",
      "enum": [
        "mine",
        "aicall"
      ],
      "title": "Callerid",
      "description": "What the people they call see: mine (their own cell, once it is verified with the carrier; until then AI Call's number) or aicall. The call to them always comes from AI Call's number",
      "default": "mine"
    },
    "agentName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 40,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Agentname",
      "description": "The name the voice agent goes by if it speaks (default: your persona's name)"
    },
    "company": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Company",
      "description": "If the call is for a business, the business: the voice agent then speaks as that team"
    },
    "context": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "business",
            "personal"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Context",
      "description": "As in call_book; default: on the person's behalf"
    },
    "maxMinutes": {
      "type": "integer",
      "maximum": 120.0,
      "minimum": 1.0,
      "title": "Maxminutes",
      "description": "Hard limit for the call length in minutes (1 to 120, default 60)",
      "default": 60
    },
    "scheduledAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Scheduledat",
      "description": "Ring them later instead of now (ISO 8601 with a UTC offset), as in call_book"
    },
    "idempotencyKey": {
      "type": "string",
      "pattern": "^[A-Za-z0-9._:-]{8,200}$",
      "title": "Idempotencykey",
      "description": "Required. Same key + same body returns the first booking; never rings twice"
    }
  },
  "type": "object",
  "required": [
    "idempotencyKey"
  ],
  "title": "CallMeRequest",
  "description": "call_me (Sam, 2026-10-26): \"if I just wanted to call myself, but I wanted you to join me in the call I could say to\nyou give me a call and I pick up the line and then I call out to whoever I want to and the whole thing gets recorded\nand you're listening, watching the transcript in real time and completing tasks\"."
}
Response schema (201)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid",
      "description": "Use it with every other call tool"
    },
    "state": {
      "type": "string",
      "enum": [
        "SCHEDULED",
        "QUEUED",
        "DIALING",
        "RINGING",
        "CONNECTED",
        "WRAPPING",
        "ENDED",
        "FAILED",
        "CANCELLED",
        "UNCERTAIN"
      ],
      "title": "State"
    },
    "recipient": {
      "type": "string",
      "title": "Recipient"
    },
    "who": {
      "type": "string",
      "title": "Who"
    },
    "maxMinutes": {
      "type": "integer",
      "title": "Maxminutes"
    },
    "callLink": {
      "type": "string",
      "title": "Calllink",
      "description": "https://aicall.prodevgroup.com/call/<token>: live transcript, notes, transfer, hang up, listen; read-only for 30 days after the call"
    },
    "createdAt": {
      "type": "string",
      "title": "Createdat"
    },
    "note": {
      "type": "string",
      "title": "Note",
      "description": "Plain words for the human, e.g. \"Dialing now.\""
    },
    "scheduledAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Scheduledat",
      "description": "When it dials (scheduled calls)"
    },
    "wakeAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeat",
      "description": "When to be back in the seat: set your own cron or wake-up for this time (the call.wake webhook fires then too)"
    },
    "requireSeat": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ],
      "title": "Requireseat"
    },
    "wakePrompt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Wakeprompt",
      "description": "A ready-made prompt for your own scheduled wake-up (cron, scheduled task)"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "state",
    "recipient",
    "who",
    "maxMinutes",
    "callLink",
    "createdAt",
    "note"
  ],
  "title": "CallBooking"
}
POST/api/v1/calls/{bookingId}/dialscope: calls:book_approved | calls:book_new

Ring someone into a call_me call (call_dial). MCP tool call_dial. On a call_me call your person is on: rings number into it, from their own cell when callerId is mine and it is verified. One other party at a time. Watch call_wait for control events (dial DIALING, CONNECTED, NO_ANSWER, BUSY, FAILED).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/dial" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"number": "string"}'
Request body schema
{
  "properties": {
    "number": {
      "type": "string",
      "title": "Number",
      "description": "E.164 to ring into the call (an approved contact, or calls:book_new)"
    },
    "who": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Who",
      "description": "Who that is, for the call page"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key"
    }
  },
  "type": "object",
  "required": [
    "number"
  ],
  "title": "CallDialRequest",
  "description": "call_dial: on a call_me call, ring someone into it."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
POST/api/v1/calls/{bookingId}/mutescope: calls:coach

Mute or unmute the voice agent (call_mute). MCP tool call_mute. muted true: the voice agent keeps listening (the call is still transcribed) and says nothing, until muted false. On a call_me call, muted false brings it onto the line if it is not there yet; send a call_note with what you want it to do. Watch call_wait for control events (mute ON, OFF).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/mute" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"muted": true}'
Request body schema
{
  "properties": {
    "muted": {
      "type": "boolean",
      "title": "Muted",
      "description": "true: it keeps listening (and the call keeps being transcribed) but says nothing; false: it can speak again (on a call_me call this brings it onto the line)"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key"
    }
  },
  "type": "object",
  "required": [
    "muted"
  ],
  "title": "CallMuteRequest",
  "description": "call_mute: the voice agent listens and says nothing, or speaks again."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
POST/api/v1/calls/{bookingId}/holdscope: calls:coach

Have the voice agent take a hold (call_hold). MCP tool call_hold. On a call_me call where the other side put your person on hold: the voice agent takes over and says nothing (not to music, recordings or menus) until a live person is back, then tells them one moment (tellThem) and brings your person back: if they are still on the line they hear it, if they hung up their phone rings. Same as *8 on their keypad. Watch call_wait for control events (hold HOLDING, BACK).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/hold" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Request body schema
{
  "properties": {
    "tellThem": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 300,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Tellthem",
      "description": "What it tells them when a live person comes back, before bringing the person back (default: one moment, it is grabbing them)"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key"
    }
  },
  "type": "object",
  "title": "CallHoldRequest",
  "description": "call_hold: on a call_me call, the voice agent takes over a hold."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
POST/api/v1/calls/{bookingId}/voicescope: calls:coach

Switch the voice agent's voice live (call_voice). MCP tool call_voice. The voice agent speaks in another voice from its next sentence, on this call only (the booking, persona and profile keep theirs: change those with call_profile_update or call_template_save). After a switch it says a line or two in the new voice and asks how it sounds. Use it when your person asks for a different voice on the call, or in a relay event. Watch call_wait for the control event (voice CHANGED). On a call to your person's own phone the agent can also switch itself when they ask. With voiceModel (grok or qwen) the call moves to that engine to compare voices: it picks up a second or two later with the call so far in writing; watch for the control event (voice_model SWITCHED or FAILED).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/voice" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Request body schema
{
  "properties": {
    "voice": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Voice",
      "description": "xAI voice id, or a Qwen voice name on Qwen, or a GPT-Live voice on GPT-Live (one of its own, or voice_... for one of the account's own); it speaks in it from its next sentence (GPT-Live: a moment later, in a fresh session). The booking and profile keep theirs"
    },
    "voiceModel": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "grok",
            "qwen",
            "gpt"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Voicemodel",
      "description": "Move the call to this engine (for comparing voices): it picks up a second or two later with the call so far in writing. voice, if given, is its voice there"
    },
    "commandId": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[A-Za-z0-9._:-]{8,200}$"
        },
        {
          "type": "null"
        }
      ],
      "title": "Commandid",
      "description": "Idempotency key"
    }
  },
  "type": "object",
  "title": "CallVoiceRequest",
  "description": "call_voice: the voice agent switches to another voice, or to the other engine, on the live call."
}
Response schema (202)
{
  "properties": {
    "commandId": {
      "type": "string",
      "title": "Commandid"
    },
    "state": {
      "type": "string",
      "const": "QUEUED",
      "title": "State"
    },
    "action": {
      "type": "string",
      "enum": [
        "transfer",
        "hangup",
        "mute",
        "dial",
        "hold",
        "voice",
        "model"
      ],
      "title": "Action"
    },
    "mode": {
      "type": "string",
      "title": "Mode"
    },
    "target": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Target"
    },
    "graceSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Graceseconds",
      "description": "hangup soft: seconds before the line drops (45)"
    }
  },
  "type": "object",
  "required": [
    "commandId",
    "state",
    "action",
    "mode"
  ],
  "title": "ControlAccepted"
}
GET/api/v1/calls/{bookingId}/filesscope: any active token (the one that booked, or the seat)

The call's photos, files and camera frames (call_files). MCP tool call_files. Newest first by limit (default 20, up to 100), returned oldest first so camera frames read as a sequence: {files[] {fileId, name, type, size, at, secondsIntoCall, source camera|page, url}}. source camera: frames (about one a second) and photos from the camera link the voice agent texted; page: what your person sent you from the call page. Line them up with the transcript by at.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book
limitqueryinteger
sourcequerystring

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/files" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "files": {
      "items": {
        "properties": {
          "fileId": {
            "type": "string",
            "title": "Fileid"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "type": {
            "type": "string",
            "title": "Type"
          },
          "size": {
            "type": "integer",
            "title": "Size"
          },
          "url": {
            "type": "string",
            "title": "Url",
            "description": "GET it with your own key"
          },
          "at": {
            "type": "string",
            "title": "At"
          },
          "secondsIntoCall": {
            "type": "integer",
            "title": "Secondsintocall"
          },
          "source": {
            "type": "string",
            "enum": [
              "camera",
              "page"
            ],
            "title": "Source",
            "description": "camera: a frame or photo from the camera link; page: sent from the call page"
          }
        },
        "type": "object",
        "required": [
          "fileId",
          "name",
          "type",
          "size",
          "url",
          "at",
          "secondsIntoCall",
          "source"
        ],
        "title": "CallFileItem"
      },
      "type": "array",
      "title": "Files"
    }
  },
  "type": "object",
  "required": [
    "files"
  ],
  "title": "CallFiles",
  "description": "call_files: the call's newest photos, files and camera frames, oldest of them first."
}
GET/api/v1/calls/{bookingId}/files/{fileId}scope: any active token (the one that booked, or the seat)

A photo or file the person sent the seat (call_file). MCP tool call_file. The bytes of a photo or file your person sent you from the call page: a relay event in call_wait with file {fileId, name, type, size, url}. Read it and use it for the call (a document to check, a photo of damage, a claim letter).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book
fileIdpathrequiredstring

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/files/<fileId>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/calls/{bookingId}/resultscope: any active token (the one that booked)

Transcript and outcome (call_result). MCP tool call_result. The durable record: outcome from the carrier (answered, no answer, busy, failed), who hung up, duration, the notes injected and when, the agent's questions with their answers, texts, and the transcript of both sides with timestamps. No summary: write your own.

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/calls/<bookingId>/result" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid"
    },
    "state": {
      "type": "string",
      "enum": [
        "SCHEDULED",
        "QUEUED",
        "DIALING",
        "RINGING",
        "CONNECTED",
        "WRAPPING",
        "ENDED",
        "FAILED",
        "CANCELLED",
        "UNCERTAIN"
      ],
      "title": "State"
    },
    "outcome": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "answered",
            "no_answer",
            "busy",
            "failed",
            "canceled"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Outcome"
    },
    "hungUpBy": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "caller",
            "agent",
            "coach",
            "owner",
            "timeout",
            "unknown"
          ]
        },
        {
          "type": "null"
        }
      ],
      "title": "Hungupby"
    },
    "recipient": {
      "type": "string",
      "title": "Recipient"
    },
    "who": {
      "type": "string",
      "title": "Who",
      "default": ""
    },
    "callerId": {
      "type": "string",
      "title": "Callerid",
      "default": ""
    },
    "startedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Startedat"
    },
    "answeredAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Answeredat"
    },
    "endedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Endedat"
    },
    "durationSeconds": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Durationseconds"
    },
    "notes": {
      "items": {
        "properties": {
          "noteId": {
            "type": "string",
            "title": "Noteid"
          },
          "source": {
            "type": "string",
            "enum": [
              "owner",
              "coach",
              "owner_text",
              "answer",
              "system"
            ],
            "title": "Source",
            "description": "owner: the person on the call page; coach: an agent (call_note); owner_text: the person's text to the line; answer: a reply to the agent's ask_for_help; system: AI Call itself (wrap-up, timeouts)"
          },
          "author": {
            "type": "string",
            "title": "Author"
          },
          "kind": {
            "type": "string",
            "title": "Kind",
            "default": "detail"
          },
          "mode": {
            "type": "string",
            "enum": [
              "guidance",
              "speak"
            ],
            "title": "Mode"
          },
          "text": {
            "type": "string",
            "title": "Text"
          },
          "state": {
            "type": "string",
            "enum": [
              "HELD",
              "QUEUED",
              "APPLIED",
              "RESPONSE_REQUESTED",
              "STALE",
              "FAILED",
              "UNCERTAIN"
            ],
            "title": "State"
          },
          "queuedAt": {
            "type": "string",
            "title": "Queuedat"
          },
          "releasedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Releasedat"
          },
          "appliedAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Appliedat"
          }
        },
        "type": "object",
        "required": [
          "noteId",
          "source",
          "author",
          "mode",
          "text",
          "state",
          "queuedAt"
        ],
        "title": "NoteView"
      },
      "type": "array",
      "title": "Notes"
    },
    "questions": {
      "items": {
        "properties": {
          "questionId": {
            "type": "string",
            "title": "Questionid"
          },
          "text": {
            "type": "string",
            "title": "Text"
          },
          "state": {
            "type": "string",
            "enum": [
              "OPEN",
              "ANSWERED",
              "TIMED_OUT"
            ],
            "title": "State"
          },
          "askedAt": {
            "type": "string",
            "title": "Askedat"
          },
          "answeredAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Answeredat"
          },
          "answeredBy": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Answeredby",
            "description": "seat (call_answer), owner_text (a text reply), owner (the call page)"
          },
          "answer": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Answer"
          }
        },
        "type": "object",
        "required": [
          "questionId",
          "text",
          "state",
          "askedAt"
        ],
        "title": "QuestionView"
      },
      "type": "array",
      "title": "Questions"
    },
    "passedAlong": {
      "items": {
        "properties": {
          "text": {
            "type": "string",
            "title": "Text",
            "description": "What the person on the call wanted the team to know or do, as the agent passed it along"
          },
          "at": {
            "type": "string",
            "title": "At"
          },
          "seatConnected": {
            "type": "boolean",
            "title": "Seatconnected",
            "description": "Whether an AI was in the seat (waiting on the call) to act on it then; false: nobody was, so it is a message for later",
            "default": true
          },
          "trusted": {
            "type": "boolean",
            "title": "Trusted",
            "description": "Set by AI Call: everyone on the line then was the person on a verified line. Not trusted: information only",
            "default": false
          }
        },
        "type": "object",
        "required": [
          "text",
          "at"
        ],
        "title": "PassedAlongView"
      },
      "type": "array",
      "title": "Passedalong",
      "description": "Instructions and feedback the agent passed to the team during the call (relay events)"
    },
    "assistant": {
      "items": {
        "properties": {
          "by": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "By",
            "description": "The seat that wrote it"
          },
          "text": {
            "type": "string",
            "title": "Text"
          },
          "doing": {
            "type": "boolean",
            "title": "Doing",
            "description": "What it was doing then; false: what it found",
            "default": false
          },
          "texted": {
            "type": "boolean",
            "title": "Texted",
            "description": "It was also texted to the person",
            "default": false
          },
          "at": {
            "type": "string",
            "title": "At"
          }
        },
        "type": "object",
        "required": [
          "text",
          "at"
        ],
        "title": "AssistantLineView"
      },
      "type": "array",
      "title": "Assistant",
      "description": "The seat's own lines (call_assistant, call_text_owner): what it did and found, never spoken"
    },
    "texts": {
      "items": {
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "out",
              "in"
            ],
            "title": "Direction"
          },
          "address": {
            "type": "string",
            "title": "Address"
          },
          "body": {
            "type": "string",
            "title": "Body"
          },
          "state": {
            "type": "string",
            "title": "State"
          },
          "at": {
            "type": "string",
            "title": "At"
          }
        },
        "type": "object",
        "required": [
          "direction",
          "address",
          "body",
          "state",
          "at"
        ],
        "title": "TextView"
      },
      "type": "array",
      "title": "Texts"
    },
    "proposals": {
      "items": {
        "additionalProperties": true,
        "type": "object"
      },
      "type": "array",
      "title": "Proposals",
      "description": "Texts the agent asked to send to the other side: {proposalId, to, body, state SENT|DECLINED|EXPIRED|FAILED, decidedBy}"
    },
    "transcript": {
      "items": {
        "properties": {
          "speaker": {
            "type": "string",
            "title": "Speaker",
            "description": "caller | agent | owner | transfer:<number>"
          },
          "at": {
            "type": "string",
            "title": "At"
          },
          "text": {
            "type": "string",
            "title": "Text"
          },
          "interrupted": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Interrupted",
            "description": "agent line the other side talked over: its end may not have been heard"
          }
        },
        "type": "object",
        "required": [
          "speaker",
          "at",
          "text"
        ],
        "title": "TranscriptLine"
      },
      "type": "array",
      "title": "Transcript"
    },
    "callLink": {
      "type": "string",
      "title": "Calllink"
    },
    "recording": {
      "anyOf": [
        {
          "additionalProperties": true,
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "title": "Recording",
      "description": "{url, seconds}: the call's audio (MP3) when the person records calls; kept 30 days. The url needs calls:listen"
    },
    "minutes": {
      "properties": {
        "callee": {
          "type": "integer",
          "title": "Callee",
          "description": "The line to the person called: answered to ended, rounded up"
        },
        "transfer": {
          "type": "integer",
          "title": "Transfer",
          "description": "The transfer target on the line: from the moment it connected to the end, rounded up; 0 when the call was not transferred"
        },
        "agent": {
          "type": "integer",
          "title": "Agent",
          "description": "The voice agent on the line: answered to the handover (or to the end without a transfer), rounded up"
        }
      },
      "type": "object",
      "required": [
        "callee",
        "transfer",
        "agent"
      ],
      "title": "Minutes",
      "description": "Billable minutes by leg. Each leg of each call is rounded up to a whole minute (carrier style); see src/aicall/usage.py."
    },
    "line": {
      "anyOf": [
        {
          "additionalProperties": true,
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "title": "Line",
      "description": "{theirAudioLostPercent, choppy, note}: how much of the other side's audio reached AI Call; choppy at 5% or more (their phone line or the route to it, not the agent). Calls in LiveKit rooms only; null when not measured"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "state",
    "outcome",
    "hungUpBy",
    "recipient",
    "startedAt",
    "answeredAt",
    "endedAt",
    "durationSeconds",
    "notes",
    "transcript",
    "callLink",
    "minutes"
  ],
  "title": "CallResult"
}
GET/api/v1/calls/upcomingscope: any active token

Your calls not finished yet (call_upcoming). MCP tool call_upcoming. Scheduled, queued and live calls booked with this token, soonest first, each with your seatBrief (the note you left yourself when booking). Call it when you wake for a scheduled call, then call_wait the booking.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/calls/upcoming" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "calls": {
      "items": {
        "properties": {
          "bookingId": {
            "type": "string",
            "title": "Bookingid"
          },
          "state": {
            "type": "string",
            "enum": [
              "SCHEDULED",
              "QUEUED",
              "DIALING",
              "RINGING",
              "CONNECTED",
              "WRAPPING",
              "ENDED",
              "FAILED",
              "CANCELLED",
              "UNCERTAIN"
            ],
            "title": "State"
          },
          "recipient": {
            "type": "string",
            "title": "Recipient"
          },
          "who": {
            "type": "string",
            "title": "Who"
          },
          "goal": {
            "type": "string",
            "title": "Goal"
          },
          "scheduledAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scheduledat"
          },
          "wakeAt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Wakeat"
          },
          "requireSeat": {
            "type": "boolean",
            "title": "Requireseat"
          },
          "seatBrief": {
            "type": "string",
            "title": "Seatbrief",
            "description": "Your note to yourself from booking time"
          },
          "callLink": {
            "type": "string",
            "title": "Calllink"
          }
        },
        "type": "object",
        "required": [
          "bookingId",
          "state",
          "recipient",
          "who",
          "goal",
          "scheduledAt",
          "wakeAt",
          "requireSeat",
          "seatBrief",
          "callLink"
        ],
        "title": "UpcomingCall"
      },
      "type": "array",
      "title": "Calls",
      "description": "This token's calls not finished yet (scheduled, queued or live), soonest first"
    }
  },
  "type": "object",
  "required": [
    "calls"
  ],
  "title": "UpcomingCalls"
}
GET/api/v1/small-talk/topicspublic

Small-talk topics for call_book.smallTalk. The bank of everyday topics the phone agent can use in a lull (a topic and an angle, never a line). Pass ids in call_book.smallTalk, or your own topic as text. Live items (home weather, the person's teams, holidays) are added on their own.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/small-talk/topics"

Memory

GET/api/v1/memoryscope: calls:listen

What AI Call remembers about a number (call_memory). MCP tool call_memory. What AI Call remembers about a phone number for the person your token acts for: the running summary of everything with them (who they are, claim and account numbers, what is still agreed, owed or open) and the call history, newest first, each call's summary (authorizations, agreements, issues, key details, next steps; never its transcript). Memory is kept per person and number from their finished calls; the phone agent reads the same notes on its own calls. Use it before calling someone or when a call turns to them. Open one call with call_memory_read. The MCP tool also takes a query and then returns matches, the list GET /memory/search gives for that number.

Parameters

nameintypedescription
numberqueryrequiredstringE.164 phone number, e.g. +12565550123 (in a URL write the + as %2B)
rolePlayquerybooleantrue: the role-play memory, kept apart from real calls (practice calls, booked with rolePlay)

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/memory?number=<number>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "number": {
      "type": "string",
      "title": "Number"
    },
    "label": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Label",
      "description": "Who they are: the contact's label, else how the call was booked"
    },
    "summary": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Summary",
      "description": "The running summary of everything with them: who they are, claim and account numbers, what is still agreed, owed or open. Rewritten after each call; null before the first is written"
    },
    "updatedAt": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Updatedat",
      "description": "When the running summary was last rewritten (ISO 8601)"
    },
    "calls": {
      "items": {
        "properties": {
          "bookingId": {
            "type": "string",
            "title": "Bookingid",
            "description": "Open it with call_memory_read"
          },
          "at": {
            "type": "string",
            "title": "At",
            "description": "When this number came on the line (ISO 8601)"
          },
          "minutes": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Minutes",
            "description": "How long they were on the line, in minutes; null while the call is still going"
          },
          "outcome": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Outcome",
            "description": "How the call ended: answered, no_answer, busy, failed, canceled"
          },
          "summary": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Summary",
            "description": "Gist, authorizations, agreements, issues, key details and next steps; null until it is written (a call that just ended)"
          }
        },
        "type": "object",
        "required": [
          "bookingId",
          "at",
          "minutes",
          "outcome",
          "summary"
        ],
        "title": "MemoryCall"
      },
      "type": "array",
      "title": "Calls",
      "description": "Every call that number was on, newest first: each call's summary, never its transcript"
    },
    "matches": {
      "anyOf": [
        {
          "items": {
            "properties": {
              "bookingId": {
                "type": "string",
                "title": "Bookingid",
                "description": "Open it with call_memory_read"
              },
              "number": {
                "type": "string",
                "title": "Number"
              },
              "label": {
                "type": "string",
                "title": "Label",
                "description": "Who it was with: the contact's label, else the number"
              },
              "at": {
                "type": "string",
                "title": "At",
                "description": "When this number came on the line (ISO 8601)"
              },
              "said": {
                "type": "string",
                "title": "Said",
                "description": "The words that matched, with a little around them"
              }
            },
            "type": "object",
            "required": [
              "bookingId",
              "number",
              "label",
              "at",
              "said"
            ],
            "title": "MemoryMatch"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "title": "Matches",
      "description": "Only from the MCP tool call_memory when it is given a query: the same list GET /memory/search returns, for that number"
    }
  },
  "type": "object",
  "required": [
    "number",
    "label",
    "summary",
    "updatedAt",
    "calls"
  ],
  "title": "CallMemory"
}
GET/api/v1/memory/calls/{bookingId}scope: calls:listen

Open one earlier call (call_memory_read). MCP tool call_memory_read. One earlier call from call_memory: its summary (authorizations, agreements, issues, key details, next steps) first, its condensed transcript (part transcript, only what was said while that number was on the line) only when you need more. A call that had several numbers on the line is kept once per number: pass number for the one you want, else the latest. 404 when the call is not in memory (a call is written up once it ends).

Parameters

nameintypedescription
bookingIdpathrequiredstringbookingId from call_book
partquerystringsummary (default), or transcript: the condensed transcript
numberquerystringE.164 phone number, e.g. +12565550123 (in a URL write the + as %2B). Only that number's side of the call
rolePlayquerybooleantrue: the role-play memory, kept apart from real calls (practice calls, booked with rolePlay)

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/memory/calls/<bookingId>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "bookingId": {
      "type": "string",
      "title": "Bookingid"
    },
    "number": {
      "type": "string",
      "title": "Number",
      "description": "Whose side of the call this is (a call that had several numbers on the line is kept once per number)"
    },
    "label": {
      "type": "string",
      "title": "Label",
      "description": "Who it was with: the contact's label, else the number"
    },
    "at": {
      "type": "string",
      "title": "At",
      "description": "When this number came on the line (ISO 8601)"
    },
    "part": {
      "type": "string",
      "enum": [
        "summary",
        "transcript"
      ],
      "title": "Part"
    },
    "summary": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Summary",
      "description": "part summary: gist, authorizations, agreements, issues, key details, next steps; null when none was written (see note)"
    },
    "transcript": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Transcript",
      "description": "part transcript: only what was said while this number was on the line, one line per turn, filler left out"
    },
    "note": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Note",
      "description": "e.g. that no summary was written, so read the transcript"
    }
  },
  "type": "object",
  "required": [
    "bookingId",
    "number",
    "label",
    "at",
    "part"
  ],
  "title": "CallMemoryRead"
}

Usage

GET/api/v1/usagescope: any active token (the person's whole account)

Call minutes for a month (usage). Minutes metered for the person your token acts for, over a UTC calendar month (month=YYYY-MM, default the current month): totals, split by call engine, and per token (every token the person holds, not just yours; token IDs and agent names only). Answered calls count once they have ended, in the month they were answered. Three legs per call, each rounded up to a whole minute: callee (answered to ended), transfer (the target connected to ended; 0 without a transfer) and agent (the voice agent on the line: answered to the handover, or to the end). call_result carries the same three numbers for one call as minutes. AI Call counts minutes; it does not price them.

Parameters

nameintypedescription
monthquerystringUTC calendar month, YYYY-MM (default: the current month)

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/usage" \
  -H "Authorization: Bearer $AICALL_TOKEN"
Response schema (200)
{
  "properties": {
    "month": {
      "type": "string",
      "title": "Month",
      "description": "The UTC calendar month, YYYY-MM"
    },
    "from": {
      "type": "string",
      "title": "From",
      "description": "First instant of the month (ISO 8601, UTC)"
    },
    "to": {
      "type": "string",
      "title": "To",
      "description": "First instant of the next month (ISO 8601, UTC); the month ends just before it"
    },
    "calls": {
      "type": "integer",
      "title": "Calls",
      "description": "Answered calls that ended and were answered in this month"
    },
    "minutes": {
      "properties": {
        "callee": {
          "type": "integer",
          "title": "Callee",
          "description": "The line to the person called: answered to ended, rounded up"
        },
        "transfer": {
          "type": "integer",
          "title": "Transfer",
          "description": "The transfer target on the line: from the moment it connected to the end, rounded up; 0 when the call was not transferred"
        },
        "agent": {
          "type": "integer",
          "title": "Agent",
          "description": "The voice agent on the line: answered to the handover (or to the end without a transfer), rounded up"
        }
      },
      "type": "object",
      "required": [
        "callee",
        "transfer",
        "agent"
      ],
      "title": "Minutes",
      "description": "Billable minutes by leg. Each leg of each call is rounded up to a whole minute (carrier style); see src/aicall/usage.py."
    },
    "byEngine": {
      "additionalProperties": {
        "properties": {
          "calls": {
            "type": "integer",
            "title": "Calls"
          },
          "minutes": {
            "$ref": "#/components/schemas/Minutes"
          }
        },
        "type": "object",
        "required": [
          "calls",
          "minutes"
        ],
        "title": "UsageEngine"
      },
      "propertyNames": {
        "enum": [
          "bridge",
          "livekit"
        ]
      },
      "type": "object",
      "title": "Byengine",
      "description": "The same, split by call engine (bridge: Twilio Media Streams; livekit: LiveKit rooms). Calls from before the engine was recorded count as bridge"
    },
    "tokens": {
      "items": {
        "properties": {
          "tokenId": {
            "type": "string",
            "title": "Tokenid",
            "description": "The token's ID (tok_...), never the token itself"
          },
          "agentName": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agentname",
            "description": "The agent name the token goes by (falls back to the token's name)"
          },
          "calls": {
            "type": "integer",
            "title": "Calls"
          },
          "minutes": {
            "$ref": "#/components/schemas/Minutes"
          }
        },
        "type": "object",
        "required": [
          "tokenId",
          "agentName",
          "calls",
          "minutes"
        ],
        "title": "UsageToken"
      },
      "type": "array",
      "title": "Tokens",
      "description": "Per token, most minutes first. Only tokens with a call in the month; removed tokens included"
    }
  },
  "type": "object",
  "required": [
    "month",
    "from",
    "to",
    "calls",
    "minutes",
    "byEngine",
    "tokens"
  ],
  "title": "UsageView"
}

Webhooks

GET/api/v1/webhooksscope: any active token

List this token's webhooks.

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/webhooks" \
  -H "Authorization: Bearer $AICALL_TOKEN"
POST/api/v1/webhooksscope: any active token

Create a webhook for this token's calls. Deliveries are JSON POSTs {id, event, createdAt, data} with headers x-aicall-signature (sha256=HMAC-SHA256(secret, "<timestamp>.<raw body>")), x-aicall-timestamp, x-aicall-event and x-aicall-delivery (Pro Sign's scheme). Events: call.connected, call.question (the phone agent asked for help: answer with call_answer), call.relay (the phone agent passed along an instruction or feedback from the call: act on it), call.ended (data = call_result), and call.seat (only when listed; needs calls:listen and calls:coach): it makes this token one of the person's seats, and an incoming call that needs one is handed to the first free seat with data {callId, bookingId, reason: ring | next | rejoin, seat}; take the seat with call_wait on callId until the call ends (a seat that has not joined within a minute is passed over, one that left is woken again). With bearer the receiver also gets Authorization: Bearer <bearer>. Failed deliveries are retried with backoff for about a day. The secret is only returned here.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/webhooks" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "string"}'
Request body schema
{
  "properties": {
    "url": {
      "type": "string",
      "maxLength": 2000,
      "minLength": 10,
      "title": "Url",
      "description": "https URL that receives JSON POSTs"
    },
    "events": {
      "items": {
        "type": "string",
        "enum": [
          "call.wake",
          "call.connected",
          "call.question",
          "call.relay",
          "call.payment",
          "call.ended",
          "call.seat"
        ]
      },
      "type": "array",
      "title": "Events",
      "description": "Empty = all but call.seat"
    },
    "bearer": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500,
          "minLength": 8
        },
        {
          "type": "null"
        }
      ],
      "title": "Bearer",
      "description": "Sent as Authorization: Bearer <bearer> on every delivery, for a receiver that wants its own key (a Grok Bot routine's webhook key). Stored and never shown again."
    }
  },
  "type": "object",
  "required": [
    "url"
  ],
  "title": "WebhookBody"
}
DELETE/api/v1/webhooks/{webhookId}scope: any active token

Delete a webhook.

Parameters

nameintypedescription
webhookIdpathrequiredstring

Example

curl -X DELETE "https://aicall.prodevgroup.com/api/v1/webhooks/<webhookId>" \
  -H "Authorization: Bearer $AICALL_TOKEN"
GET/api/v1/seat/wakeupscope: any active token

This token's wake-up for incoming calls. {active, request}: the wake-up in use (kind email or webhook) and the latest request with its status (PENDING, APPROVED, DECLINED, REPLACED, EXPIRED).

Example

curl -X GET "https://aicall.prodevgroup.com/api/v1/seat/wakeup" \
  -H "Authorization: Bearer $AICALL_TOKEN"
POST/api/v1/seat/wakeupscope: calls:listen and calls:coach

Ask to be woken for the person's incoming calls. Makes this token one of the person's seats once the person approves (an email with a one-tap approval page, as for tokens). Send email (your own address) or url and key. When an incoming call needs a seat and this one is free, AI Call sends {callId, bookingId, reason: ring | next | rejoin, seat, caller: {number, name}} there: an email with subject "AI Call seat: <callId>" and a caller: line, or a call.seat webhook. call_wait carries caller too. Take the seat with call_wait on callId until the call ends (a seat that has not joined within a minute is passed over, one that left is woken again). A new request replaces one still waiting; once approved it replaces the token's wake-up. 5 per hour per token.

Example

curl -X POST "https://aicall.prodevgroup.com/api/v1/seat/wakeup" \
  -H "Authorization: Bearer $AICALL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Request body schema
{
  "properties": {
    "email": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 254
        },
        {
          "type": "null"
        }
      ],
      "title": "Email",
      "description": "Your own email address: a routine of yours that runs when mail arrives there is woken by an email whose subject and body carry the callId"
    },
    "url": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ],
      "title": "Url",
      "description": "Or a public https webhook (a JSON POST of the call.seat event)"
    },
    "key": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ],
      "title": "Key",
      "description": "With url: sent as Authorization: Bearer <key> (a Grok Bot routine's webhook key). Stored and never shown again"
    }
  },
  "type": "object",
  "title": "SeatWakeupBody"
}