API reference

API reference

Everything the dashboard does, it does through this HTTP API — so anything you see in the console you can automate. JSON in, JSON out, session-based auth.

Base URL

base url
https://avatarx.net/api

The API and the app are served from the same origin, so requests from a signed-in browser session are same-site — there is no CORS configuration to maintain. From a server or script, call it directly.

Interactive docs

A generated OpenAPI browser is available at /docs when the server runs with debug enabled. This page is the hand-written summary.

Authentication

Sign in once and you get a server-side session. The response sets an HttpOnly session_id cookie and returns the same id in the body, so scripts that cannot hold cookies can send it as a bearer token instead. Both are accepted.

sign in
curl -X POST https://avatarx.net/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","password":"••••••••"}'

# {
#   "session_id": "…",
#   "token_type": "bearer",
#   "expires_at": "2026-10-21T09:12:00",
#   "user": { "id": "…", "email": "you@example.com", "is_superuser": false }
# }
authenticated request
curl https://avatarx.net/api/agents/ \
  -H 'Authorization: Bearer YOUR_SESSION_ID'

Sessions are revocable server-side, so there is no refresh flow: a 401 means the session is gone and you should sign in again.

POST/api/auth/registerCreate an account
POST/api/auth/loginStart a session
GET/api/auth/meCurrent user
POST/api/auth/logoutEnd this session
POST/api/auth/logout-allEnd every session
POST/api/auth/change-passwordRotate the password
GET/api/auth/sessionsList active devices
DELETE/api/auth/sessions/{session_id}Revoke one device
GET/api/auth/google/urlGoogle consent URL
POST/api/auth/google/completeFinish a Google flow

Conventions

  • Bodies are JSON unless the endpoint takes a file, in which case it is multipart/form-data.
  • Ids are UUID strings. Everything is scoped to the caller: another account's agent returns 404, never 403.
  • Errors carry a detail field. Validation errors return 422 with an array of field problems.
  • Deletes answer 204 with no body. Long jobs (indexing, crawling, avatar prep) answer 202 and you poll for status.
  • On PATCH, an omitted key is left alone and an explicit null clears the column — that is how you remove a word cap or a topic list.
error shape
{ "detail": "Agent not found" }

Agents

GET/api/agents/List your agents
POST/api/agents/Create one
GET/api/agents/{agent_id}Fetch one
PATCH/api/agents/{agent_id}Update
DELETE/api/agents/{agent_id}Delete
GET/api/agents/templatesStarter templates
GET/api/agents/{agent_id}/effective-promptComposed prompt + temperature

Creating an agent

Only name and system_prompt are required. Send behaviour as structured fields — role, personality, topics to avoid and the word cap are composed into the prompt server-side at call time, so they stay readable and editable instead of being baked into prose.

POST /api/agents/
{
  "name": "Support desk",
  "system_prompt": "You help visitors with billing and account questions.",
  "personality": "Friendly and Professional",
  "agent_role": "Customer support specialist",
  "language": "en",
  "voice_id": "db6b0ed5-d5d3-463d-ae85-518a07d3c2b4",
  "musetalk_avatar_id": "ava",
  "llm_provider": "openai",
  "llm_model": "gpt-4o",
  "creativity": 0.4,
  "knowledge_mode": "strict",
  "greeting": "Hi! How can I help today?",
  "conversation_starters": ["Where is my order?", "How do I cancel?"],
  "topics_to_avoid": ["competitor pricing"],
  "max_response_words": 80,
  "is_public": true
}
FieldTypeNotes
creativity0–1Maps to model temperature; effective_temperature is returned.
knowledge_mode"strict" | "hybrid"Alias for restrict_to_knowledge.
topics_to_avoidstring[] | null≤ 20 items, ≤ 80 chars each.
conversation_startersstring[] | null≤ 4 items, ≤ 120 chars each.
max_response_wordsint | null10–500, or null for no cap.
pronunciations{ word, say_as }[]≤ 50 entries.
is_publicboolMust be true for the embeddable widget to load.

Text chat

POST/api/agents/preview/chatTry an unsaved prompt
POST/api/agents/{agent_id}/chatChat with a real agent

The agent endpoint composes the prompt, retrieves from the knowledge base and matches media, returning the reply plus whatever it decided to show. The preview endpoint persists nothing and is rate-limited to 20 messages a minute per user.

POST /api/agents/{agent_id}/chat
{ "messages": [{ "role": "user", "content": "do you ship to Spain?" }] }

# {
#   "reply": "Yes — standard delivery to Spain takes 3–5 days.",
#   "media": { "id": "…", "kind": "image", "title": "Shipping rates", "url": "/api/media/…/file" },
#   "used_knowledge": true
# }

Knowledge

GET/api/knowledge/agents/{agent_id}/summaryCounts per source
POST/api/knowledge/agents/{agent_id}/bootstrapText + URLs in one call
GET/api/knowledge/agents/{agent_id}/search?q=&k=What the agent would retrieve
GET/api/knowledge/agents/{agent_id}/kb/List knowledge bases
POST/api/knowledge/agents/{agent_id}/kb/Create one
PATCH/api/knowledge/agents/{agent_id}/kb/{kb_id}Rename
DELETE/api/knowledge/agents/{agent_id}/kb/{kb_id}Delete
POST/api/knowledge/agents/{agent_id}/kb/{kb_id}/documentsUpload a file (multipart)
POST/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/textAdd pasted text
GET/api/knowledge/agents/{agent_id}/kb/{kb_id}/documentsList documents
GET/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/{doc_id}/contentRead full text
PUT/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/{doc_id}/contentEdit + re-embed
PATCH/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/{doc_id}Rename only
POST/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/{doc_id}/reindexRe-run indexing
DELETE/api/knowledge/agents/{agent_id}/kb/{kb_id}/documents/{doc_id}Delete a document
POST/api/knowledge/agents/{agent_id}/kb/from-urlAttach a website
GET/api/knowledge/agents/{agent_id}/kb/from-url/statusCrawl status
POST/api/knowledge/agents/{agent_id}/kb/{kb_id}/recrawlRe-crawl

bootstrap is the one to use when setting an agent up: it creates the default knowledge base if absent, stores pasted text, queues a crawl per URL, and reports each part separately.

POST …/bootstrap
{
  "text": "We ship worldwide. Returns accepted within 30 days.",
  "urls": ["https://example.com/faq"],
  "restrict_to_knowledge": true
}

# 202 { "kb_id": "…", "created": { "text": true, "urls": ["https://example.com/faq"] }, "errors": [] }

Uploads accept PDF, TXT, MD and DOCX up to 20 MB. Documents return pending → processing → ready; poll the document list while anything is in flight.

Media

GET/api/agents/{agent_id}/mediaList items
POST/api/agents/{agent_id}/mediaUpload a file, or JSON for a link
PATCH/api/agents/{agent_id}/media/{media_id}Edit title, keywords, order
DELETE/api/agents/{agent_id}/media/{media_id}Delete (also unlinks the file)
GET/api/media/{media_id}/fileStream the file — public for a public agent
POST …/media (multipart)
curl -X POST https://avatarx.net/api/agents/AGENT_ID/media \
  -H 'Authorization: Bearer YOUR_SESSION_ID' \
  -F 'file=@spring-flyer.png' \
  -F 'title=Spring flyer' \
  -F 'trigger_keywords=flyer,offers,discount' \
  -F 'show_by_default=false'

PNG, JPG, WEBP, GIF, SVG, PDF, MP4 and WEBM are accepted, up to 25 MB and 50 items per agent.

Tools

GET/api/toolsSystem + custom tools
POST/api/toolsCreate a webhook tool
PATCH/api/tools/{tool_id}Update (custom only)
DELETE/api/tools/{tool_id}Delete (custom only)
POST/api/tools/{tool_id}/testDry-run against the real endpoint
GET/api/agents/{agent_id}/toolsWhat this agent runs with
PUT/api/agents/{agent_id}/toolsReplace the attached set
POST /api/tools
{
  "name": "check_order_status",
  "description": "Look up an order by its number. Call this whenever a caller asks where their order is.",
  "method": "POST",
  "url": "https://api.example.com/orders/status",
  "auth_type": "bearer",
  "auth_config": { "token": "•••" },
  "timeout_seconds": 10,
  "parameters": {
    "type": "object",
    "properties": { "order_number": { "type": "string" } },
    "required": ["order_number"]
  }
}

auth_config is write-only: it is stored encrypted and never returned. Responses carry auth_type and a has_credentials flag instead. System tools appear with system: ids and cannot be edited or deleted.

Outbound guard

Tool URLs must be HTTPS and must not resolve to loopback, private, link-local or CGNAT ranges — checked again after redirects, with at most two hops, a 32 KB response cap and a 30 second ceiling.

CRM & scheduling

GET/api/agents/{agent_id}/crm/settingsCapabilities + scheduling rules
PUT/api/agents/{agent_id}/crm/settingsUpdate them
GET/api/agents/{agent_id}/crm/summaryCounters
GET/api/agents/{agent_id}/crm/leadsList leads
POST/api/agents/{agent_id}/crm/leadsCreate manually
PATCH/api/agents/{agent_id}/crm/leads/{lead_id}Edit status or fields
DELETE/api/agents/{agent_id}/crm/leads/{lead_id}Delete
GET/api/agents/{agent_id}/crm/leads/export/csvCSV export
GET/api/agents/{agent_id}/crm/appointmentsList bookings
POST/api/agents/{agent_id}/crm/appointmentsBook manually
PATCH/api/agents/{agent_id}/crm/appointments/{appt_id}Cancel / complete / no-show
GET/api/agents/{agent_id}/crm/availabilityFree slots the agent would offer
PUT …/crm/settings
{
  "enable_lead_capture": true,
  "enable_appointments": true,
  "enable_human_handoff": true,
  "meet_link": "meet.google.com/abc-defg-hij",
  "appointment_config": {
    "timezone": "Europe/Madrid",
    "slot_minutes": 30,
    "buffer_minutes": 10,
    "min_notice_minutes": 60,
    "max_days_ahead": 14,
    "business_hours": {
      "mon": [["09:00", "13:00"], ["14:00", "17:00"]],
      "tue": [["09:00", "17:00"]],
      "sat": [], "sun": []
    }
  }
}

Avatars & voices

GET/api/avatars/catalogueEvery avatar you can pick
GET/api/avatars/mineYour uploads and their status
POST/api/avatars/Upload one (multipart)
GET/api/avatars/{avatar_id}/previewPreview image
DELETE/api/avatars/{avatar_id}Delete
GET/api/voices/Available voices
GET/api/voices/preview?url=Stream a voice sample

Custom avatars are not finished

Uploads are stored and tracked, but there is no preparation pipeline yet: photo avatars settle as ready and video uploads come back failed with an explanation. The gallery avatars work today.

Conversations & analytics

GET/api/conversations/sessionsList conversations
GET/api/conversations/sessions/{session_id}One conversation + messages
POST/api/conversations/sessionsOpen a session
POST/api/conversations/sessions/{session_id}/messagesAppend a message
POST/api/conversations/sessions/{session_id}/endClose it
GET/api/analytics/overviewHeadline numbers
GET/api/analytics/timeseriesVolume over time
GET/api/analytics/agentsPer-agent breakdown
GET/api/credits/{agent_id}/balanceRemaining minutes
POST/api/credits/{agent_id}/topupAdd minutes

Live calls are brokered by POST /api/livekit/join, which checks credits, issues a room token and dispatches the agent worker. It answers 402 with credits_exhausted when the balance is gone.

Public endpoints

These need no session — they are what the embedded widget calls from a visitor's browser.

GET/api/widget/{agent_id}/configName, greeting, starters, media, avatar, voice
GET/api/widget/{agent_id}/onboardingWhether to show the pre-chat form
POST/api/widget/{agent_id}/onboardingSave visitor answers
GET/api/media/{media_id}/fileMedia file for a public agent
GET/healthService health

The config endpoint only serves agents that are public and active, and never returns the system prompt or any account data.

Settings & support

GET/api/settings/smtpOutbound email config (password never returned)
PUT/api/settings/smtpUpdate it
POST/api/settings/smtp/testSend a test message
DELETE/api/settings/smtpRemove it
GET/api/settings/googleCalendar connection status
DELETE/api/settings/googleDisconnect
GET/api/ticketsYour support tickets
POST/api/ticketsOpen one
POST/api/tickets/{ticket_id}/replyReply
POST/api/tickets/{ticket_id}/closeClose

Administrator accounts have a further /api/admin/* surface (accounts, agents, sessions, audit log, health, tickets) which always requires a real superuser session.

End-to-end example

Sign in, create an agent, give it knowledge, and check what it retrieves.

bash
BASE=https://avatarx.net/api

# 1. sign in
SID=$(curl -s -X POST $BASE/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","password":"••••••••"}' | jq -r .session_id)
AUTH="Authorization: Bearer $SID"

# 2. create the agent
AID=$(curl -s -X POST $BASE/agents/ -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{
        "name":"Support desk",
        "system_prompt":"You help visitors with billing and account questions.",
        "knowledge_mode":"strict",
        "greeting":"Hi! How can I help today?",
        "is_public":true
      }' | jq -r .id)

# 3. give it something to know
curl -s -X POST $BASE/knowledge/agents/$AID/bootstrap -H "$AUTH" \
  -H 'Content-Type: application/json' \
  -d '{"text":"Refunds are issued within 30 days of purchase.","urls":[]}'

# 4. see what it would retrieve (indexing takes a moment)
curl -s -G $BASE/knowledge/agents/$AID/search -H "$AUTH" --data-urlencode 'q=refund window'

# 5. talk to it
curl -s -X POST $BASE/agents/$AID/chat -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"can I get a refund after 3 weeks?"}]}'

Then embed it — see Embedding the widget.