REST API

REST API

MAIA public REST API. Base URL:

https://api.maia-ai.com

Overview

Every request must be authenticated with one of these headers:

X-API-Key: maia_YOUR_KEY

or

Authorization: Bearer maia_YOUR_KEY

Keys are created in the dashboard at dashboard.maia-ai.com → “API and MCP” section → “API” tab → “Create key”. The key is shown only once. See Authentication for details.

Scopes:

  • read — read: projects, calls, contacts, campaigns, knowledge base, lead search, numbers.
  • write — write and launch: outbound calls, contact and knowledge edits, calling campaigns, lead search, number assignment.

All data is strictly scoped to the key owner: only their projects and related entities are reachable. Business logic (subscription/balance gates, consent under Russian law, moderation, provisioning) is the same as in the dashboard.

Pagination

List endpoints accept query parameters and return a wrapper object:

ParameterTypeDefaultDescription
limitint50 (max 200)How many records to return
offsetint0Offset from the start of the result set
{ "items": [ /* … */ ], "total": 128, "limit": 50, "offset": 0 }

Projects

List projects

GET /v1/public/projects — scope read.

Returns the projects (agents) owned by the key.

curl https://api.maia-ai.com/v1/public/projects \
  -H "X-API-Key: maia_YOUR_KEY"
{
  "projects": [
    { "id": "3f9a2b10-6c7d-4e88-9f12-0a1b2c3d4e5f", "name": "Sales team", "language": "ru" }
  ]
}

Calls

MethodPathScopeDescription
GET/v1/public/callsreadCall log (filters project_id, direction, status; paginated)
GET/v1/public/calls/{call_id}readCall details: summary, extracted data, transcript
POST/v1/public/callswritePlace an outbound call

Place an outbound call

POST /v1/public/calls — scope write.

Request body:

FieldTypeRequiredDescription
project_idstring (uuid)yesProject the call is placed from
phonestringyesNumber in +79991234567 format
taskstringnoTask instruction for the agent on this call
varsobjectnoVariables for {{var}} substitution in the prompt

Pre-flight checks: project ownership, active subscription, balance ≥ price per minute (otherwise 402), an assigned outbound line (otherwise 409).

curl -X POST https://api.maia-ai.com/v1/public/calls \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "3f9a2b10-6c7d-4e88-9f12-0a1b2c3d4e5f",
    "phone": "+79991234567",
    "task": "Confirm tomorrow appointment at 3pm",
    "vars": { "client_name": "John" }
  }'

Contacts

Unified contacts (the same data as the dashboard “Contacts”).

MethodPathScopeDescription
GET/v1/public/contactsreadContact list (filters q, project_id, channel, lead; paginated)
PATCH/v1/public/contacts/{contact_id}writeEdit a contact card

Edit a contact

PATCH /v1/public/contacts/{contact_id} — scope write. Fields not sent are left unchanged.

FieldTypeDescription
namestringContact name
notesstringNotes
tagsarrayTags (replace the whole list)
lead_statusstringnew | in_progress | hot | won | lost; "" clears the status
curl -X PATCH https://api.maia-ai.com/v1/public/contacts/a1b2c3d4-1111-2222-3333-444455556666 \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "lead_status": "hot", "tags": ["vip", "callback"] }'

Knowledge base

Project knowledge sources. Indexing runs in the background — a source moves through pending → indexing → ready | error; check its status in the list.

MethodPathScopeDescription
GET/v1/public/projects/{project_id}/knowledgereadList sources (id, kind, name, status, chunks)
POST/v1/public/projects/{project_id}/knowledgewriteAdd a source: text or URL
POST/v1/public/projects/{project_id}/knowledge/filewriteUpload a file source (multipart)
DELETE/v1/public/projects/{project_id}/knowledge/{source_id}writeDelete a source (with chunks and file)

POST …/knowledge body: type (text | url), title, content (for text), url, crawl (crawl the site), max_pages (1–50, default 15). Files — txt/md/csv/html/json up to 5 MB, PDF/DOCX up to 20 MB — go through the multipart route …/knowledge/file.

curl -X POST https://api.maia-ai.com/v1/public/projects/3f9a2b10-6c7d-4e88-9f12-0a1b2c3d4e5f/knowledge \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "url", "url": "https://example.com/faq", "crawl": true, "max_pages": 15 }'

Calling campaigns

Mass outbound calling of a contact list. Flow: create → upload contacts → start (with consent) → track progress.

MethodPathScopeDescription
GET/v1/public/campaignsreadCampaign list (+counters, price per minute, spend)
POST/v1/public/campaignswriteCreate a campaign (draft)
GET/v1/public/campaigns/{id}readCampaign: status, params, counters, spend
POST/v1/public/campaigns/{id}/contactswriteUpload contacts (JSON, before start)
GET/v1/public/campaigns/{id}/contactsreadCampaign contacts (filters status, goal)
POST/v1/public/campaigns/{id}/startwriteStart (requires consent=true)
POST/v1/public/campaigns/{id}/pause | /resume | /stopwritePause / resume / stop
POST/v1/public/campaigns/{id}/requeuewriteRe-call the ones not reached (no_answer/busy/failed)

params on creation (as in the dashboard): timezone, window_start, window_end, concurrency, retries, retry_interval_min, scheduled_start_at. Contacts are loaded as a JSON array [{phone, name?, vars?}] (E.164 normalization, dedup by number, mode = append | replace) and only before start (otherwise 409).

⚠️

Starting a campaign requires consent=true — an explicit confirmation that you have the subscribers’ consent to be called (Russian laws 152-FZ/38-FZ). Without it — 400. Other gates: active subscription, uploaded contacts, an assigned outbound line, sufficient balance.

# start a campaign with confirmed subscriber consent
curl -X POST https://api.maia-ai.com/v1/public/campaigns/CAMPAIGN_ID/start \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "consent": true }'

Find and enrich leads from a brief. Flow: parse the brief (free) → run the paid search → track progress → export leads or turn them straight into a calling campaign.

MethodPathScopeDescription
POST/v1/public/lead-search/parse-briefwriteFree brief parse: structure + estimate + providers
POST/v1/public/lead-searchwriteRun a paid search
GET/v1/public/lead-searchreadSearch jobs (last 50)
GET/v1/public/lead-search/{job_id}readJob status and progress (poll)
GET/v1/public/lead-search/{job_id}/leadsreadJob leads (filters status, has_phone, min_score, q)
POST/v1/public/lead-search/{job_id}/cancelwriteSoft-cancel the search
POST/v1/public/lead-search/{job_id}/to-campaignwriteBuild a draft campaign from leads with phones

POST /lead-search body: brief_raw, brief_parsed (structure from parse-brief: niche, geo, signals, exclusions, target_leads, queries) and accept_terms (a one-time legal checkbox on the first run). You are billed for enriched leads actually found.

Launch gates: active subscription, brief moderation (422), unaccepted legal terms (400 without accept_terms on the first run), daily job limit (429), an already active job (409), estimate vs balance (402).

# 1) free brief parse — returns the estimate and available providers
curl -X POST https://api.maia-ai.com/v1/public/lead-search/parse-brief \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "brief_raw": "beauty salons in Kazan, 50 leads" }'

Numbers (telephony)

The key owner’s SIP-trunk numbers and their assignment to projects by direction (in — inbound, out — outbound, both — both). For calls and campaigns a project needs an outbound line (out/both).

MethodPathScopeDescription
GET/v1/public/numbersreadAll trunk numbers with assignment status
POST/v1/public/projects/{project_id}/numberswriteAssign a number to a project
DELETE/v1/public/projects/{project_id}/numbers/{number}writeUnassign a number (query direction — only that direction)
curl -X POST https://api.maia-ai.com/v1/public/projects/3f9a2b10-6c7d-4e88-9f12-0a1b2c3d4e5f/numbers \
  -H "X-API-Key: maia_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "number": "+74951234567", "direction": "both" }'

Real-time voice session

POST /v1/public/realtime/session — scope write.

Opens a live voice session with the project’s agent over WebRTC: a device, app, or SDK talks to the agent in real time. The response (LiveKit room access) and connection examples (JS, Python, Swift, Kotlin) are on the dedicated Real-time voice page.

Error codes

CodeMeaning
400Body validation error; missing consent (campaign start) or accept_terms (lead search)
401Key missing or invalid
402Insufficient balance (below price per minute or estimate)
403Key lacks the required scope
404Object not found (or not owned by the key)
409No outbound SIP line; campaign contacts after start; a lead-search job already active
422Brief or prompt rejected by moderation
429Daily lead-search launch limit or key rate limit exceeded

Full reference

Interactive OpenAPI specification with all fields: API Reference.