Four HTTP surfaces, each opened by its own bearer token. Every endpoint here is read from the code that serves it, so the field names on this page are the field names you get back.
Base URLhttps://magiz.ai
Getting started
Every surface speaks JSON over HTTPS and expects an Accept: application/json header. Send your token in the Authorization header, and read the status code before the body: a rejected request always carries a message field explaining itself.
JSON only
Requests and responses are JSON. The one exception is the widget streaming endpoint, which returns Server-Sent Events.
Workspace scoped
A token only ever reaches the workspace it was minted in. Asking for a chatbot or conversation owned by another account returns 404, never another workspace data.
Stateless
No cookies, no CSRF token, no session. Each request carries its own credentials, so the same call works from a server, a phone app or a cron job.
Created in the dashboard under Settings, in the Rest API tokens panel. Name it, copy the value once (it is shown only then), and use it for the v1 endpoints. An account may hold up to 20, and revoking one takes effect immediately.
ability: api
Portal token
Minted by posting a dashboard email and password to the portal login endpoint. Only an owner or admin of a workspace that has the white-label portal switched on can obtain one.
ability: portal
Agent token
Minted by posting an email and password to the agent login endpoint. Owners, admins and agents may sign in; a read-only viewer seat is refused. The apps rotate the token before it lapses.
ability: agent
Widget endpoints
No token at all. The chatbot public key sits in the URL, and a visitor proves a conversation is theirs by sending the same visitor id that opened it.
The token types are not interchangeable. A token made on the Settings screen opens the v1 endpoints only: send it to a portal or agent route and you get 403, because those routes require the portal and agent abilities that a Settings token does not carry. Portal and agent tokens come from their own login endpoints.
Every token expires 30 days after it is issued. The agent apps rotate theirs through the refresh endpoint; for a server integration, mint a replacement before the old one lapses.
Errors
A failed request returns the matching HTTP status and a JSON body. Validation failures follow the Laravel shape: a readable message plus an errors object keyed by field name.
{
"message": "The message field is required.",
"errors": {
"message": ["The message field is required."]
}
}
Status
What causes it
401
The token is missing, malformed, expired or revoked.
403
The token is valid but not allowed here: the wrong ability for this surface, a role without the permission, or a suspended account or workspace.
404
The record does not exist, or it belongs to another workspace. The two cases are deliberately indistinguishable.
409
The request conflicts with the current state, for example inviting a visitor who has already left the site.
422
Validation failed, or a business rule refused the request. Read the errors object to see which field and why.
429
A rate limit or an account lockout was hit. Wait the number of seconds given in the Retry-After header.
500
Something failed on our side. The action did not complete.
502
The AI provider could not be reached. Check the key configured for the workspace.
503
A capability is not configured on this server, for example web push with no VAPID keys.
Rate limits
Limits are set per route and every endpoint below states its own. On authenticated routes the count is per signed-in user; on public routes it is per IP address.
A throttled response carries the usual headers, so a rejection tells you exactly how long to wait.
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
Retry-After: 41
Endpoints marked "No route limit" carry no throttle of their own. They are still bound by your plan monthly message allowance, so poll them at a sensible interval rather than in a tight loop.
The widget on your site
One script tag installs the assistant on every page. Everything below is optional: the widget works with nothing but the key.
Required. Your chatbot public key, from Chatbots then Install.
data-open="load"
Opens the panel as soon as the page loads. Use it on a page whose whole purpose is the assistant, such as a chat with AI page, and leave it off everywhere else.
data-launcher
Forces the launcher style for this page, overriding the dashboard setting. Accepts bubble or teaser. Useful when the teaser would cover a form.
data-identity
A signed identity token for the person viewing the page. See below.
data-cookie-domain
Scope for the cookie that keeps a conversation across subdomains, for example .example.com. Only needed when the domain has a multi part suffix such as .co.uk and the automatic guess is wrong.
data-lang
The language of the chat window's own words and its direction, for visitors who read another language than the page: en, fr or ar. Without it the window follows the page's lang, then the browser's language.
data-consent="wait"
Set to wait when your cookie banner decides. Until your page calls MagizAI.consent(true), the widget stores nothing in the visitor's browser (no visitor ID cookie, nothing in localStorage or sessionStorage) and sends no live visitor or browsing data. A visitor can still open the chat before that; it then works for that page only.
Calling the widget from your page
The script exposes a small object on window once it loads. Every method is safe to call before the visitor has opened the chat.
// Open the panel, optionally with the question already asked.
MagizAI.open();
MagizAI.open('Which plan suits a site with 20,000 visits a month?');
// Close it again.
MagizAI.close();
// Call this when the visitor signs out: it ends this browser's conversation
// and forgets whose it was, so the next person starts anonymously.
MagizAI.logout();
// Record a sale the chat contributed to, for the revenue report.
MagizAI.trackConversion(149.00, 'USD');
// With your cookie banner (data-consent="wait" on the script tag):
// the visitor agreed, so the widget may store its visitor ID...
MagizAI.consent(true);
// ...or said no: what the widget stored leaves this browser.
MagizAI.consent(false);
MagizAI.open() with a question is how an Ask about this plan button works: the panel opens and the question is already sent, so the visitor sees an answer rather than an empty box. Sites embedded before the rename can keep calling window.ElyAgents, which is the same object.
MagizAI.consent(true) stores the visitor ID and starts live visitors and browsing tracking, each where it is switched on and the browser does not send Do Not Track. MagizAI.consent(false) stops them for that page and removes what the widget stored in this browser: the visitor ID, the conversation link, the details the visitor gave and the widget's own preferences; an open chat goes on until the page is left. The widget does not remember the answer, so call it on every page once your banner knows it; a call made before the widget has loaded is kept. Without a banner, the chatbot setting "Store a visitor ID only after the visitor opens the chat" holds everything back the same way until the chat is opened.
Signed visitor identity
Without it the assistant treats everyone as anonymous and will not discuss an account. With it, the assistant knows who is asking and can answer about their own orders, invoices, services or tickets. Your backend signs a short compact token, HS256, using the identity secret on the chatbot; the browser only carries it.
<?php
// Server-side, in the page your signed-in customer is served. Never in the browser:
// whoever holds the secret can impersonate any of your customers.
$secret = getenv('MAGIZAI_IDENTITY_SECRET'); // Chatbots → Install → Signed visitor identity
$b64 = fn (array $a) => rtrim(strtr(base64_encode(json_encode($a)), '+/', '-_'), '=');
$header = $b64(['alg' => 'HS256', 'typ' => 'JWT']);
$payload = $b64([
'sub' => (string) $user->id, // your id for this person
'external_id' => (string) $user->id,
'email' => $user->email,
'name' => $user->name,
'plan' => $user->plan, // optional, shown to your agents
'company' => $user->company_name, // optional
'locale' => 'en', // optional
'iat' => time(),
'exp' => time() + 3600, // 24 hours is the hard ceiling
]);
$sig = rtrim(strtr(base64_encode(hash_hmac('sha256', "$header.$payload", $secret, true)), '+/', '-_'), '=');
$token = "$header.$payload.$sig";
?>
<script>window.MagizAIIdentity = "<?= $token ?>";</script>
<script src="https://magiz.ai/widget.js" data-key="cv_9f2a4c7e1b6d3a8f5c0e2b4d" defer></script>
Parameter
Description
sub
Required. Your own id for this person. The conversation and its memory are bound to it. Never "0": a token that names "0" is refused.
external_id
Optional alias for sub, accepted so a backend can send whichever field it already has. The same rule applies: never "0".
email
Used to match the visitor to orders, tickets and past chats, and shown to your agents.
name
Shown to your agents and used by the assistant when it greets the visitor.
plan
Optional. Any label you use for tier or segment. Your agents see it on the customer panel.
company
Optional. The account or organisation name.
locale
Optional. Two letter language code, used when the chatbot is set to follow the signed in user rather than the browser.
iat / exp
Issued at and expiry, in seconds. A token is never honoured for more than 24 hours, whatever exp says. Mint a fresh one per page load.
Sign on the server and never ship the secret to the browser: anyone holding it can impersonate any of your customers. Call MagizAI.logout() when the visitor signs out, so a shared computer does not hand the next person the previous conversation.
Webhooks
The one part of the platform that calls you. Add an endpoint under Automations, on the Webhooks tab, choose the events you care about, and we POST signed JSON to your URL when they happen.
Event
What causes it
conversation.created
A visitor opened a conversation, before anything has been said.
message.received
A visitor sent a message.
ai.replied
The assistant answered. Useful for logging what was said to a customer.
keyword.matched
A keyword you configured appeared in a visitor message.
handoff.requested
The conversation asked for a human, whether the visitor asked or the assistant escalated. This is the one to wire to your alerting.
handoff.followup
A visitor left contact details so your team can reply later. Sent instead of handoff.requested when the chat is already waiting for a person, or when the last handoff got no answer. Wire it to your alerting too.
conversation.resolved
A conversation was closed, by an agent or by the assistant. The usual trigger for closing your own ticket.
tag.added
A tag was applied to a conversation.
lead.captured
Contact details were captured. The usual trigger for creating a CRM record.
lead.request
A visitor confirmed a request for your team in the chat, such as a callback or a quote. The contact and what they asked for arrive under data.lead.
conversion.recorded
Revenue was attributed to a conversation.
action.executed
The assistant ran one of your actions, such as a skill or a connected tool.
booking.created
A booking was made and needs no approval. The booking arrives under data.booking, with no link that could change it.
booking.requested
A booking request came in and waits for your team to confirm or decline it.
booking.confirmed
A booking request was confirmed.
booking.declined
A booking request was declined.
booking.cancelled
A booking was cancelled, by the customer or by your team.
booking.rescheduled
A booking was moved to another time.
booking.completed
A booking was marked done.
booking.no_show
A customer did not come to their booking.
Every delivery has the same envelope: the event name, when it happened, and a data object. Eloquent records are never serialised raw, so data carries plain fields only, and a conversation always arrives under data.conversation with its public_id.
Each request carries X-Signature: sha256= followed by an HMAC of the exact bytes we sent, keyed with your endpoint secret. Check it before you trust the body, and compare in constant time.
// Node / Express. Verify against the RAW body, before any JSON parsing:
// re-serialising changes the bytes and the signature will never match.
app.post('/hooks/magizai', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.MAGIZAI_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(req.get('X-Signature') || '');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);
const { event, data } = JSON.parse(req.body.toString('utf8'));
// Answer first, work afterwards: anything slower than 10 seconds counts as a failure.
res.sendStatus(200);
queue.push({ event, data });
});
Rule
Description
Public HTTPS only
The URL must resolve to a public address. Internal and loopback addresses are refused, the connection is pinned to the verified address, and redirects are not followed.
10 second timeout
Answer with a 2xx as soon as you have the payload and do the work afterwards. Anything slower counts as a failed delivery.
3 attempts
A failure or an answer outside 2xx is tried again up to twice, a few seconds to a minute apart, so three attempts in all. After the third the delivery stays marked failed in the log. An address the public address check refuses is not tried again.
No ordering guarantee
Deliveries are queued, so two events can arrive out of order and a retry can arrive twice. Treat the payload as a fact to apply rather than a step to replay, and key on public_id.
Every delivery is logged
Status, response code and the first 2000 characters of your response are kept, and Send test posts a sample payload. Read that log first when something does not arrive.
REST API (v1)
The general-purpose integration API. Read your chatbots and conversations, add knowledge, and send a visitor message to get the AI reply back in a single JSON response. Authenticated with a token you create on the Settings screen.
Base path/api/v1·9 endpoints
GET/api/v1/me
Returns the user the token belongs to, and their workspace.
Lists the documents a chatbot holds, newest first, as a paginated result.
Auth Bearer token (api)Rate limit No route limit
The pages a crawled URL expanded into are left out, so the count matches what an admin sees on the Knowledge screen. Filter by source_url_prefix to ask only about the documents your own integration pushed, which is how a sync reconciles what it sent with what is actually there.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
type
string
Optional
Return only this kind: text, qa, url or file.
source_url_prefix
string
Optional
Return only documents whose address starts with this.
per_page
integer
Optional
Query. Rows per page. Defaults to 20 and is capped at 100.
A field failed validation. The errors object names it.
POST/api/v1/chatbots/{key}/knowledge
Adds a text or question-and-answer document to a chatbot and trains it immediately.
Auth Bearer token (api)Role abilitymanage-contentRate limit No route limit
Training runs inside the request rather than on a queue, so the call blocks until the document is indexed. Expect several seconds for a long document.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
title
string
Required
A name for the document. Up to 200 characters.
content
string
Required
The document text.
type
string
Optional
text or qa. Defaults to text.
source_url
string
Optional
Required when type is url. The page to crawl, up to 2048 characters.
visibility
string
Optional
public or customer. A customer document is only used when the visitor is identified by a signed token.
Request
curl -X POST https://magiz.ai/api/v1/chatbots/cv_9f2a4c7e1b6d3a8f5c0e2b4d/knowledge \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Refund policy",
"type": "text",
"source_url": "https://example.com/help/refund-policy",
"content": "We refund any order within 30 days of delivery. Ship it back with the original packing slip and we credit the original payment method within five working days."
}'
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
A field failed validation. The errors object names it.
422
The document was saved but could not be trained. The response carries its id and status so you can retry training instead of uploading again.
DELETE/api/v1/chatbots/{key}/knowledge
Removes pushed documents: the one at source_url, or everything under url_prefix except the addresses you list in keep.
Auth Bearer token (api)Role abilitymanage-contentRate limit No route limit
This is how a sync removes articles that were deleted at the source: send the URLs you still have as keep, and anything else you once pushed under that prefix goes. It never touches crawled pages, uploads, or documents added by hand in the dashboard.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
source_url
string
Optional
Remove the single document pushed with this address. Required unless url_prefix is given.
url_prefix
string
Optional
Remove every pushed document whose address starts with this. Must include a path, not just a domain.
keep
array
Optional
Addresses to spare when using url_prefix. Up to 5000.
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
A field failed validation. The errors object names it.
422
url_prefix has no path. A bare domain would sweep every document the chatbot holds, so it is refused.
POST/api/v1/chatbots/{key}/messages
Sends a visitor message and returns the AI reply in one response, without streaming.
Auth Bearer token (api)Role abilityhandle-conversationsRate limit No route limit
The reply is generated in full before the response is sent. For tokens as they arrive, use the widget streaming endpoint instead. When a human has taken the conversation over, reply is null and note explains why.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
message
string
Required
The visitor message. Up to 4000 characters, and it cannot be only whitespace.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
conversation_id
uuid
Optional
An existing conversation to continue. Leave it out to start a new one.
meta
object
Optional
Free-form context stored on the conversation.
Request
curl -X POST https://magiz.ai/api/v1/chatbots/cv_9f2a4c7e1b6d3a8f5c0e2b4d/messages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"visitor_id": "v_2c9a71b4e5",
"message": "How long does delivery to Dubai take?"
}'
Response
{
"conversation_id": "6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91",
"status": "bot",
"reply": {
"id": 33127,
"content": "Orders to Dubai arrive in two to four working days with DHL Express. You get a tracking link by email as soon as the parcel leaves our warehouse.",
"model": "claude-haiku-4-5",
"tokens_in": 812,
"tokens_out": 41
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
A field failed validation. The errors object names it.
429
The workspace has reached its monthly message limit.
502
The AI provider could not be reached. Check the key configured for the workspace.
GET/api/v1/conversations/{conversation}
Returns one conversation with its full transcript.
{
"data": {
"id": "6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91",
"status": "bot",
"visitor_id": "v_2c9a71b4e5",
"messages": [
{
"id": 33126,
"role": "visitor",
"content": "How long does delivery to Dubai take?",
"created_at": "2026-08-08T09:41:04.000000Z"
},
{
"id": 33127,
"role": "assistant",
"content": "Orders to Dubai arrive in two to four working days with DHL Express.",
"created_at": "2026-08-08T09:41:07.000000Z"
}
]
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
The account or the workspace has been suspended.
404
No such record in this workspace.
AI API
MagizAI's AI for your own software, with no chatbot: write, rewrite, summarise, extract, classify, translate, and answer questions from your business's trained knowledge. It can also start a run of one of the workspace's AI agents and read how it went. A workspace manager switches on the AI API app, then makes a token with "Can use the AI API" ticked in Settings, API tokens. Each call counts as one AI message on the plan, like a chat reply, and as one of the plan's AI API calls this month. Every error carries a stable code: app_off, no_ability, invalid, health_off, cap, rate_limited, no_ai, failed, not_found, agents_off, agents_stopped or agent_unavailable. Do not send health records unless the workspace has switched on AI for health data, and then mark the call with contains_health_data.
Base path/api/v1/ai·5 endpoints
POST/api/v1/ai/complete
Runs one AI task on the text you send. Returns text, or fields for extract and classify.
Auth Bearer token (api)Role abilityaiRate limit 60 requests every 1 min, per token
The result is a suggestion: check it before it reaches a customer or changes a record. For extract, a field the text does not give is null. For classify, each field holds one of its options, or null.
Parameters
Parameter
Type
Required
Description
purpose
string
Required
One of write, rewrite, summarise, extract, classify, translate, answer.
input
string
Required
The text to work on: the brief for write, the text with its question for answer. Up to 10000 characters.
language
string
Optional
Write in this language, as a code such as fr or a name such as French. Required for translate. Default: the language of the text.
tone
string
Optional
One of neutral, friendly, formal, warm.
fields
array
Optional
For extract and classify: up to 25 field names, or objects with name, description and options. Classify needs options for every field.
contains_health_data
boolean
Optional
true when the text carries health data. Refused unless the workspace switched on AI for health data.
Request
curl -X POST https://magiz.ai/api/v1/ai/complete \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"purpose": "extract",
"input": "Hi, this is Sara Bell. My order A-1055 has not arrived in Dubai yet.",
"fields": ["name", "order_number", "city"]
}'
no_ability: the token was made without the ai ability, or its owner can no longer manage the workspace.
403
health_off: the call is marked contains_health_data and AI for health data is off.
422
invalid: a field failed validation. The errors object names it.
429
cap: this month's AI API calls, or the plan's AI messages, are used up.
429
rate_limited: too many calls with this token in a minute. Retry-After says when to try again.
502
failed: the AI did not give a usable answer. Try again.
503
no_ai: the workspace has no AI that can answer now (no working AI key, or the workspace is not active).
POST/api/v1/ai/answer
Answers a question from the workspace's trained knowledge and lists the sources it used.
Auth Bearer token (api)Role abilityaiRate limit 30 requests every 1 min, per token
Reads only public passages unless audience is staff. knowledge says what the search found: used, none_found, no_knowledge or unavailable. When nothing matches, the model is told to say so rather than guess.
Parameters
Parameter
Type
Required
Description
question
string
Required
The question. Up to 2000 characters.
language
string
Optional
Write in this language, as a code such as fr or a name such as French. Required for translate. Default: the language of the text.
audience
string
Optional
public (the default) reads only public passages. staff also reads passages for signed-in customers and internal notes.
contains_health_data
boolean
Optional
true when the text carries health data. Refused unless the workspace switched on AI for health data.
Request
curl -X POST https://magiz.ai/api/v1/ai/answer \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"question": "Do you deliver to Dubai, and how long does it take?"
}'
Response
{
"answer": "Yes. Orders to Dubai arrive in two to four working days with DHL Express.",
"knowledge": "used",
"sources": [
{
"title": "Shipping and delivery",
"url": "https://acme.test/shipping"
}
]
}
Errors
Status
What causes it
401
No valid token was sent.
403
app_off: the AI API app is off for the workspace.
403
no_ability: the token was made without the ai ability, or its owner can no longer manage the workspace.
403
health_off: the call is marked contains_health_data and AI for health data is off.
422
invalid: a field failed validation. The errors object names it.
429
cap: this month's AI API calls, or the plan's AI messages, are used up.
429
rate_limited: too many calls with this token in a minute. Retry-After says when to try again.
502
failed: the AI did not give a usable answer. Try again.
503
no_ai: the workspace has no AI that can answer now (no working AI key, or the workspace is not active).
GET/api/v1/ai/agents
Lists the workspace's active agents that a token may run.
Auth Bearer token (api)Role abilityaiRate limit 60 requests every 1 min, per token
An agent that works with health details is not listed: it never runs through the API. can_act is true when the agent may ask for actions, which wait for a manager in the dashboard. Needs the Agents app switched on.
agents_off: the Agents app is off for the workspace.
403
no_ability: the token was made without the ai ability, or its owner can no longer manage the workspace.
429
rate_limited: too many calls with this token in a minute. Retry-After says when to try again.
POST/api/v1/ai/agents/{id}/runs
Starts a run of one agent with a task. Answers at once with the run, or when it ends within wait seconds.
Auth Bearer token (api)Role abilityaiRate limit 10 requests every 1 min, per token
The run keeps every rule of the Agents app: the agent's own limits, the cost caps and the stop switch. Each model call counts as one AI API call and one AI message. Status 202 means the run is still going: read it with GET /api/v1/ai/runs/{id}. Actions and drafts wait for a manager on the run's page in the dashboard (dashboard_url); the API cannot approve them. Needs the Agents app switched on.
Parameters
Parameter
Type
Required
Description
id
integer
Required
Path. The agent's id, from GET /api/v1/ai/agents.
task
string
Required
What the agent is asked to do. Up to 4000 characters.
inputs
object
Optional
Up to 20 named values, such as {"invoice": "INV-0107"}. Names use letters, digits and _. The agent reads them as data, never as instructions.
wait
integer
Optional
Seconds to hold the call for the run to end, 0 to 25. Default 0: an answer at once with status 202.
Request
curl -X POST https://magiz.ai/api/v1/ai/agents/12/runs \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"task": "Check what Ananya Rao owes and draft a polite reminder.",
"inputs": {"customer": "Ananya Rao"},
"wait": 20
}'
Response
{
"run": {
"id": 845,
"agent": {
"id": 12,
"name": "Billing desk",
"version": 3
},
"status": "done",
"ended": "done",
"output": "Ananya Rao owes INR 4,500 on invoice INV-0107, due on 20 October. I saved a reminder email as a draft for you to check.",
"approval": "pending",
"actions": [],
"drafts": 1,
"dashboard_url": "https://magiz.ai/agents/runs/845",
"usage": {
"model_calls": 3,
"tool_calls": 2,
"tokens_in": 4120,
"tokens_out": 310,
"cost_usd": "0.004380"
},
"started_at": "2026-10-16T09:12:03.120Z",
"finished_at": "2026-10-16T09:12:09.874Z"
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
app_off: the AI API app is off for the workspace.
403
agents_off: the Agents app is off for the workspace.
403
no_ability: the token was made without the ai ability, or its owner can no longer manage the workspace.
404
not_found: no such agent in the workspace.
409
agent_unavailable: the agent is archived, or works with health details.
409
agents_stopped: a workspace manager stopped every agent.
422
invalid: a field failed validation. The errors object names it.
429
cap: this month's AI API calls or the plan's AI messages are used up, or the agents' cost cap for today is reached.
429
rate_limited: too many calls with this token in a minute. Retry-After says when to try again.
GET/api/v1/ai/runs/{id}
Reads a run started through the AI API: its state, its answer, what waits for a manager and what it used.
Auth Bearer token (api)Role abilityaiRate limit 120 requests every 1 min, per token
status is running, waiting, done, stopped or failed. ended says how it ended, such as done, waiting, step_limit, time_limit, cap or rejected. approval is pending while a manager has to decide. Runs started from the dashboard, a workflow or a chat cannot be read here.
Parameters
Parameter
Type
Required
Description
id
integer
Required
Path. The run's id, from the call that started it.
agents_off: the Agents app is off for the workspace.
403
no_ability: the token was made without the ai ability, or its owner can no longer manage the workspace.
404
not_found: no such run in the workspace started through the AI API.
429
rate_limited: too many calls with this token in a minute. Retry-After says when to try again.
Portal API
The tenant API behind the white-label portal a customer can host on their own domain. It manages chatbots, knowledge, provider keys, usage and the embed snippet. Data and AI stay on the platform, and a stored provider key is never sent back to the browser.
Base path/api/portal·20 endpoints
POST/api/portal/login
Exchanges a dashboard email and password for a portal bearer token.
Auth None, publicRate limit 10 requests every 1 min, per IP address
Failures are deliberately vague: a wrong password, an unknown address, a portal that is switched off and an insufficient role all return the same wording, so the endpoint cannot be used to discover which accounts exist. Attempts share one lockout counter with the web sign-in form.
Parameters
Parameter
Type
Required
Description
email
string
Required
The email address of a dashboard user. Up to 255 characters.
The email or password was wrong. The wording is the same whether or not the address exists.
403
The portal is switched off for this workspace, the workspace is suspended, or the user is not an owner or admin. Worded exactly like a wrong password, on purpose.
422
A field failed validation. The errors object names it.
429
Too many attempts, from the route limit or the shared account lockout. Retry-After says how long to wait.
POST/api/portal/logout
Revokes the portal token used to make the call.
Auth Bearer token (portal)Rate limit No route limit
{
"data": {
"public_key": "cv_9f2a4c7e1b6d3a8f5c0e2b4d",
"name": "Support bot",
"role": "support",
"provider": "claude",
"model": "claude-haiku-4-5",
"is_active": true,
"knowledge_sources": 14,
"conversations": 67,
"persona": "Warm, direct, never oversells.",
"instructions": "Always quote delivery times from the shipping table.",
"welcome_message": "Hi! How can I help you today?",
"branding": {
"primary_color": "#4f46e5",
"launcher_text": "Chat with us",
"position": "right",
"theme": "minimal",
"logo_url": null,
"avatar_url": null,
"title": "Support bot",
"subtitle": "We typically reply in a few seconds",
"layout": "classic",
"launcher": "bubble",
"quick_replies": [],
"agent_avatars": [],
"composer_style": "full",
"sound": true,
"screen_share": false,
"copilot": false,
"auto_actions": true,
"copilot_use_profile": false,
"copilot_autostart": true,
"live_visitors": true,
"behavior_tracking": false,
"recommend_mode": "reactive",
"proactive": {
"enabled": false,
"message": "Hi! Anything I can help you find?",
"delay": 20,
"scroll": 0,
"exit_intent": false,
"url_contains": "",
"frequency": "session"
}
}
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
The account or the workspace has been suspended.
404
No such record in this workspace.
GET/api/portal/chatbots/{key}/options
Returns every value the update endpoint will accept: AI providers and models this plan allows, quality tiers, themes, layouts, launchers, reply styles and composer styles.
Auth Bearer token (portal)Rate limit No route limit
Read this before building an edit form rather than hard coding the lists. The choices depend on the plan and on whether the workspace is using its own provider key, and they change as plans change. Provider keys themselves are never returned, only whether one is on file.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
Auth Bearer token (portal)Role abilitymanage-chatbotsRate limit No route limit
name is required on every call. Branding fields are merged over what is stored, so sending only primary_color leaves the rest untouched, and an empty welcome_message resets it to the default greeting. The AI provider and model cannot be changed here.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
name
string
Required
The chatbot name. Up to 120 characters.
persona
string
Optional
How the assistant should sound. Up to 2000 characters.
instructions
string
Optional
Standing instructions for the assistant. Up to 4000 characters.
welcome_message
string
Optional
The greeting shown when the chat opens. Up to 300 characters; empty resets it to the default.
is_active
boolean
Optional
Whether the chatbot answers. Leave it out to keep the current setting.
primary_color
string
Optional
Widget accent colour as a hex value. Up to 9 characters.
Adds a text note, a question-and-answer pair, or a URL to crawl.
Auth Bearer token (portal)Role abilitymanage-contentRate limit No route limit
Which fields are required depends on type: content for text, question and answer for qa, source_url for url. The source is created straight away with status pending and indexed in the background, so poll the list endpoint to watch it turn ready.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
type
string
Required
text, qa or url. It decides which of the fields below are required.
title
string
Optional
A name for the source. Left out, it falls back to a sensible default for the type.
content
string
Optional
Required when type is text. The note body, up to 1,000,000 characters.
question
string
Optional
Required when type is qa. Up to 2000 characters.
answer
string
Optional
Required when type is qa. Up to 50,000 characters.
source_url
url
Optional
Required when type is url. The page to crawl, up to 2048 characters.
Request
curl -X POST https://magiz.ai/api/portal/chatbots/cv_9f2a4c7e1b6d3a8f5c0e2b4d/knowledge \
-H "Authorization: Bearer $PORTAL_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"type": "qa",
"title": "Weekend delivery",
"question": "Do you deliver on Saturdays?",
"answer": "Yes. Saturday delivery is available in Dubai and Abu Dhabi at no extra cost."
}'
The token is valid, but the user role does not grant this action.
422
That provider slug is not recognised.
GET/api/portal/conversations
The conversation list, with unread counts and section totals.
Auth Bearer token (portal)Role abilityview-inboxRate limit No route limit
The same feed the dashboard inbox and the agent apps read, so the two can never drift apart. Pass filter to narrow it: all, waiting, mine, team, ai or resolved. Up to 60 rows come back, most urgent first.
Parameters
Parameter
Type
Required
Description
filter
string
Optional
Query. Narrow the list: all, waiting, mine, team, ai, human or resolved. Defaults to all.
conversation
uuid
Optional
Query. Open this conversation, return it in active and mark it read.
Replies to a visitor as a human, and takes the conversation over from the AI.
Auth Bearer token (portal)Role abilityhandle-conversationsRate limit 60 requests every 1 min, per signed-in user
Sending a reply stops the AI answering that conversation and cancels any pending handoff timer. Pass client_id on a retry: if the first attempt did reach us, the stored message is returned instead of the customer receiving it twice.
Parameters
Parameter
Type
Required
Description
conversation
uuid
Required
Path. The conversation public id, a UUID.
body
string
Required
The reply to send. Up to 4000 characters, and it cannot be only whitespace.
client_id
string
Optional
Your own id for this reply. Resending it returns the message already created instead of a second one. Up to 64 characters.
The workspace has no chatbot yet, so there is no snippet to return.
Agent app API
The API behind the agent mobile and desktop apps. It works the inbox: read conversations, reply, take over, resolve, tag, use AI assist, watch live visitors and register a device for push. Where the workspace has Queue, Bookings or Customers switched on, it also runs the front desk: the day's queue, the day's agenda and the people register. Every action runs the same tenant logic as the dashboard, so behaviour matches exactly.
Base path/api/agent·44 endpoints
POST/api/agent/login
Exchanges an email and password for an agent bearer token.
Auth None, publicRate limit 10 requests every 1 min, per IP address
Only owners, admins and agents may sign in here. A viewer seat is refused, as is any account whose workspace is suspended. Attempts share one lockout counter with the web sign-in form and the portal API. With two-step login on, the code is sent with the email and password; a workspace that requires two-step login gives no token to a person who has not turned it on.
Parameters
Parameter
Type
Required
Description
email
string
Required
The email address of a dashboard user. Up to 255 characters.
password
string
Required
That user dashboard password.
device_name
string
Optional
A label for this device, shown beside the token. Up to 120 characters.
code
string
Optional
Only when two-step login is on for this account: the 6-digit code from the authenticator app, or a recovery code, sent with the same email and password.
The email or password was wrong. The wording is the same whether or not the address exists.
401
Two-step login is on for this account: two_factor is code_required when no code was sent, wrong_code when it did not match. Code tries are limited to 6 per 5 minutes per account and address.
403
This account cannot handle conversations, or its workspace is unavailable.
403
The two_factor field says setup_required: the workspace requires two-step login of this person and it is not on. They turn it on in their profile on the web, then sign in again.
422
A field failed validation. The errors object names it.
429
Too many attempts, from the route limit or the shared account lockout. Retry-After says how long to wait.
POST/api/agent/refresh
Exchanges a still-valid agent token for a fresh one.
Auth Bearer token (agent)Rate limit 6 requests every 60 min, per signed-in user
Rotate once the token is past half its life. The old token is left to lapse on its own rather than being revoked, so a response lost in transit never signs a device out.
The two_factor field says setup_required: the workspace now requires two-step login of this person and it is not on. The token is deleted; sign in again once it is on.
429
The route limit for this user was hit.
GET/api/agent/me
Returns the signed-in agent, their workspace, and when the current token expires.
Auth Bearer token (agent)Rate limit No route limit
Authorises a realtime channel subscription for an app that holds a token instead of a session cookie.
Auth Bearer token (agent)Rate limit No route limit
Laravel own broadcasting endpoint expects a session cookie, which the apps do not have. This is the same authorisation logic, reached with a bearer token.
Returns the inbox feed: the conversation list, unread counts and section totals.
Auth Bearer token (agent)Role abilityview-inboxRate limit No route limit
One response carries at most 60 rows, while groups counts the whole inbox, so use filter to reach the rest of a section. Passing conversation returns that thread in active and marks it read.
Parameters
Parameter
Type
Required
Description
filter
string
Optional
Query. Narrow the list: all, waiting, mine, team, ai, human or resolved. Defaults to all.
conversation
uuid
Optional
Query. Open this conversation, return it in active and mark it read.
Returns one conversation in full, with its transcript, visitor profile, tags, notes and events.
Auth Bearer token (agent)Role abilityview-inboxRate limit No route limit
This is the inbox endpoint with the conversation pre-selected, so the body has the same shape and the requested thread arrives in active. An id that does not exist in this workspace is not an error: you still get 200, with active set to null. Opening a conversation marks it read.
Sends an agent reply and delivers it on whichever channel the conversation is running.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit No route limit
Sending a reply takes the conversation over automatically. Pass client_id from an offline outbox to make retries safe: a repeat with the same id returns the message already created and sets duplicate to true instead of sending a second one.
Parameters
Parameter
Type
Required
Description
conversation
uuid
Required
Path. The conversation public id, a UUID.
body
string
Required
The reply to send. Up to 4000 characters, and it cannot be only whitespace.
client_id
string
Optional
Your own id for this reply. Resending it returns the message already created instead of a second one. Up to 64 characters.
Request
curl -X POST https://magiz.ai/api/agent/conversations/6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91/reply \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"body": "I have opened a claim with the courier and posted a replacement today.",
"client_id": "outbox-2026-08-08-0007"
}'
Response
{
"message": {
"id": 33131,
"role": "agent",
"content": "I have opened a claim with the courier and posted a replacement today.",
"at": "10:47 AM"
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
The reply was only whitespace.
422
A field failed validation. The errors object names it.
GET/api/agent/attachments/options
What this agent may attach: accepted types, the size cap and how many files per send.
Auth Bearer token (agent)Role abilityview-inboxRate limit No route limit
Read it at launch rather than hard coding the limits, so a change on the platform reaches an installed app without a store release.
Sends one to five files to the visitor, with an optional caption.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 30 requests every 1 min, per signed-in user
Sending a file is replying: it takes the conversation over exactly as a text reply does, and the file is delivered on whichever channel the visitor is on. A partial batch returns 207 with the messages that were sent and the files that were refused, because discarding work that already succeeded is worse than a mixed result.
Parameters
Parameter
Type
Required
Description
conversation
uuid
Required
Path. The conversation public id, a UUID.
files[]
file
Required
One to five files, sent as multipart form data. Anything outside the accepted types, or over the size cap, is refused per file.
caption
string
Optional
Text sent with the first file. The rest carry their filename.
client_id
string
Optional
Your own id for this reply. Resending it returns the message already created instead of a second one. Up to 64 characters.
Request
curl -X POST https://magiz.ai/api/agent/conversations/6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91/attachments \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Accept: application/json" \
-F "files[]=@/path/to/replacement-label.pdf" \
-F "caption=Here is the return label." \
-F "client_id=outbox-2026-08-08-0008"
{
"canned": [
{
"id": 19,
"title": "Courier claim opened",
"shortcut": "/claim",
"body": "I have opened a claim with the courier. You will hear from us within one working day."
}
]
}
Errors
Status
What causes it
401
No valid token was sent.
403
The account or the workspace has been suspended.
POST/api/agent/canned/{canned}/used
Records that a canned reply was inserted, so the ordering keeps learning.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit No route limit
An agent may only mark a reply used if it is shared or their own.
Returns the visitor Shopify orders and Stripe subscription for the customer panel.
Auth Bearer token (agent)Role abilityview-inboxRate limit No route limit
Both integrations degrade instead of failing. connected is false when the workspace has not connected that service; shopify.orders is then an empty array and stripe.data is null. stripe.data also comes back as {"found": false} when Stripe is connected but no customer matches the visitor email.
Drafts a reply for the agent, grounded in the chatbot knowledge.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 30 requests every 1 min, per signed-in user
Costs one AI call. When there is nothing to answer yet, or no provider key is configured, the call still returns 200 with suggestion set to null. Keep the returned id so you can report the outcome.
Parameters
Parameter
Type
Required
Description
conversation
uuid
Required
Path. The conversation public id, a UUID.
draft
string
Optional
What the agent has typed so far, so the suggestion builds on it. Up to 4000 characters.
Request
curl -X POST https://magiz.ai/api/agent/conversations/6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91/suggest \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "draft": "tell her the claim is open" }'
Response
{
"id": 1204,
"suggestion": "I have opened a claim with the courier for order #8812 and posted a replacement today. You will get a new tracking link by email within the hour."
}
Errors
Status
What causes it
401
No valid token was sent.
403
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
A field failed validation. The errors object names it.
{
"summary": "Replacement for order 8812 never arrived; courier shows delivered. Claim opened, replacement posted.",
"summary_at": "a few seconds ago"
}
Errors
Status
What causes it
401
No valid token was sent.
403
The token is valid, but the user role does not grant this action.
404
No such record in this workspace.
422
No summary could be generated right now.
429
The route limit for this user was hit.
POST/api/agent/suggestion/{suggestion}/outcome
Records what the agent did with an AI suggestion.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 60 requests every 1 min, per signed-in user
Report generated, inserted, edited or dismissed, so assist quality stays measurable.
The token is valid, but the user role does not grant this action.
404
No suggestion with that id in this workspace.
422
A field failed validation. The errors object names it.
429
The route limit for this user was hit.
GET/api/agent/visitors
Lists everyone on the workspace sites right now, including people who never opened the chat.
Auth Bearer token (agent)Role abilityview-inboxRate limit No route limit
enabled tells you whether any active chatbot has live visitors switched on. poll_seconds is the interval the server wants you to use, so read it rather than hard-coding one.
Parameters
Parameter
Type
Required
Description
limit
integer
Optional
Query. How many visitors to return. Clamped between 1 and 200; defaults to 100.
Starts a chat with a browsing visitor by sending them the first message.
Auth Bearer token (agent)Role abilityview-inboxRate limit 30 requests every 1 min, per signed-in user
Creates a conversation when the visitor has none, marks it human-handled so the AI does not talk over you, and returns 201 in that case; a visitor with an open chat gets 200. If no chat window is open, the line reaches them on their next heartbeat.
Parameters
Parameter
Type
Required
Description
visitor
string
Required
Path. The visitor id from the live visitors list. Trimmed to 100 characters.
message
string
Required
The opening line to send. Up to 2000 characters, and it cannot be only whitespace.
chatbot_id
integer
Optional
Send from this chatbot. Left out, the visitor own chatbot is preferred, then any active one.
Request
curl -X POST https://magiz.ai/api/agent/visitors/v_2c9a71b4e5/invite \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "message": "Hi Nadia — I can see order 8812 on your screen. Want me to check it?" }'
Response
{
"conversation_id": "6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91",
"created": true,
"delivery_within_seconds": 25,
"message": {
"id": 33140,
"role": "agent",
"content": "Hi Nadia — I can see order 8812 on your screen. Want me to check it?",
"at": "10:52 AM"
}
}
Errors
Status
What causes it
401
No valid token was sent.
403
The account or the workspace has been suspended.
404
The visitor id was empty after trimming.
409
That visitor is no longer on the site.
422
The workspace has no active chatbot to send from.
429
The route limit for this user was hit.
500
The chat could not be started. Nothing was saved, so it is safe to try again.
GET/api/agent/push/vapid
Returns the public key a device needs before it can subscribe to push.
Auth Bearer token (agent)Rate limit No route limit
configured is false on a server where push has never been set up, which lets an app explain the situation instead of failing at the subscribe step.
Auth Bearer token (agent)Rate limit 20 requests every 1 min, per signed-in user
Upserts on the endpoint, because browsers reissue a subscription whenever the push service rotates it. Re-sending an existing endpoint updates the record rather than creating a second one.
Parameters
Parameter
Type
Required
Description
endpoint
url
Required
The push endpoint issued by the browser. Must be an https URL, up to 2048 characters.
keys
object
Required
The subscription key pair from the browser.
keys.p256dh
string
Required
The p256dh key from the browser subscription. Up to 255 characters.
keys.auth
string
Required
The auth secret from the browser subscription. Up to 255 characters.
device_name
string
Optional
A label for this device, shown beside the token. Up to 120 characters.
Updates this agent alert preferences and availability.
Auth Bearer token (agent)Rate limit No route limit
A partial update: only the fields you send change, and the events map is merged rather than replaced, so an older app version cannot wipe a setting it does not know about. Unknown event keys are ignored.
Parameters
Parameter
Type
Required
Description
push_enabled
boolean
Optional
Master switch for push alerts on this account.
sound_enabled
boolean
Optional
Whether alerts play a sound.
vibrate_enabled
boolean
Optional
Whether alerts vibrate the device.
email_enabled
boolean
Optional
Whether alerts are also sent by email.
events
object
Optional
Per-event settings, keyed by event: visitor_arrived, chat_started, visitor_message, handoff_requested, sla_breach. Merged with what is stored.
events.*.push
boolean
Optional
Whether this event sends a push. Required for each event you include.
events.*.sound
string
Optional
Sound name for this event, or null for the default. Up to 24 characters.
sound_pack
string
Optional
Name of the sound pack. Up to 24 characters.
volume
integer
Optional
Alert volume from 0 to 100.
quiet_hours_start
string
Optional
Start of quiet hours as HH:MM, read in the agent own timezone.
quiet_hours_end
string
Optional
End of quiet hours as HH:MM. It must be sent together with the start.
quiet_hours_tz
string
Optional
Timezone the quiet hours are read in, for example Asia/Dubai. Up to 64 characters.
dnd_minutes
integer
Optional
Silence alerts for this many minutes, from 0 to 1440. Send 0 to clear.
chatbot_filter
array
Optional
Only alert on these chatbot ids. Ids outside the workspace are dropped, and an empty result clears the filter.
only_my_conversations
boolean
Optional
Only alert on conversations assigned to this agent.
Quiet hours need both a start and an end time; half a window would do nothing.
422
A field failed validation. The errors object names it.
500
The settings could not be saved.
GET/api/agent/apps
Lists the app tabs this person may open in the agent apps: Queue, Bookings and Customers, each only while it is switched on for the workspace. Health apps are never listed.
Auth Bearer token (agent)Rate limit 60 requests every 1 min, per signed-in user
An app is listed when it is on in Apps and the person holds the ability its dashboard screens need. Ask again whenever the app comes to the front. A server without this endpoint answers 404: show the inbox only.
The workspace's queue lines and the front desk of one line today: the number being served, who waits in the order they will be called, who missed their turn, the last few who left and the sitting's numbers.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 120 requests every 1 min, per signed-in user
Names and phone numbers are for staff only. A person limited to some branches sees only those branches' lines. Ask again every poll_seconds while the screen is open.
Parameters
Parameter
Type
Required
Description
line
integer
Optional
Query. The line to show. Defaults to the first live line.
sitting
integer
Optional
Query. The sitting to show. Defaults to the one going now, else the next one.
app_off: the app is switched off for the workspace. A workspace manager can switch it on in Apps.
403
The token is valid, but the user role does not grant this action.
404
No such line, token or sitting in this workspace and the person's branches.
429
The route limit for this user was hit.
POST/api/agent/queue/lines/{line}/next
Next: the patient with the doctor is marked as seen, or with skip as not here, and the next one is called. With ticket, that token is called instead.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 240 requests every 1 min, per signed-in user
Send the sitting's version from your last read. A press made on an older picture changes nothing and answers 409 with code stale, so two presses never move the line twice.
Parameters
Parameter
Type
Required
Description
line
integer
Required
Path. The line's id.
sitting
integer
Required
The sitting's id, from desk.sitting.id.
version
integer
Required
desk.sitting.version from your last read.
skip
boolean
Optional
true when the patient who was called is not there.
ticket
integer
Optional
A waiting or skipped token to call in place of the next in line.
One token: here (in the clinic, or back in line after a missed turn), emergency (sees next), cancel, done (seen, call nobody yet) or skip (not here, call nobody yet).
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 240 requests every 1 min, per signed-in user
Each move goes on the line's timeline with who pressed, as on the dashboard's desk. The answer is the line's desk after the move, in the shape GET /api/agent/queue returns, with ok and a message.
app_off: the app is switched off for the workspace. A workspace manager can switch it on in Apps.
403
The token is valid, but the user role does not grant this action.
404
No such line, token or sitting in this workspace and the person's branches.
409
The token or the sitting cannot make this move now: code is ended, not_live, not_called, closed, full or stopped.
422
A field failed validation. The errors object names it.
429
The route limit for this user was hit.
POST/api/agent/queue/lines/{line}/walk-in
Gives a token at the desk on the sitting on screen, also when the sitting is full or has stopped taking tokens. Name and phone are both optional.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 60 requests every 1 min, per signed-in user
Each move goes on the line's timeline with who pressed, as on the dashboard's desk. The answer is the line's desk after the move, in the shape GET /api/agent/queue returns, with ok and a message.
Parameters
Parameter
Type
Required
Description
line
integer
Required
Path. The line's id.
sitting
integer
Required
The sitting's id, from desk.sitting.id.
name
string
Optional
Optional. Up to 80 characters.
phone
string
Optional
Optional. Up to 20 characters, with the country code.
The sitting: pause, resume, stop new tokens, give tokens again, or end it.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 60 requests every 1 min, per signed-in user
Each move goes on the line's timeline with who pressed, as on the dashboard's desk. The answer is the line's desk after the move, in the shape GET /api/agent/queue returns, with ok and a message.
Parameters
Parameter
Type
Required
Description
line
integer
Required
Path. The line's id.
sitting
integer
Required
Path. The sitting's id.
op
string
Required
pause, resume, stop, open or end. Ending counts the patient in the room as seen and ends every token still waiting.
app_off: the app is switched off for the workspace. A workspace manager can switch it on in Apps.
403
The token is valid, but the user role does not grant this action.
404
No such line, token or sitting in this workspace and the person's branches.
409
The token or the sitting cannot make this move now: code is ended, not_live, not_called, closed, full or stopped.
422
A field failed validation. The errors object names it.
429
The route limit for this user was hit.
GET/api/agent/bookings
One day's agenda on the workspace's clock: the bookings that hold time that day, with contact details and the changes each one allows now.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 120 requests every 1 min, per signed-in user
Cancelled and declined bookings free their time and are left out. Booking answers and internal notes are never returned. A person limited to some branches sees only those branches' bookings.
Parameters
Parameter
Type
Required
Description
date
string
Optional
Query. YYYY-MM-DD in the workspace's time zone. Defaults to today.
app_off: the app is switched off for the workspace. A workspace manager can switch it on in Apps.
403
The token is valid, but the user role does not grant this action.
429
The route limit for this user was hit.
POST/api/agent/bookings/{booking}/status
Changes a booking as staff do on the dashboard: confirm or decline a waiting one, cancel a confirmed one, mark one that has started as done or did not come.
Auth Bearer token (agent)Role abilityhandle-conversationsRate limit 60 requests every 1 min, per signed-in user
The change is written to the activity log, the customer gets the same message as from the dashboard, and workflows and webhooks hear the booking event.
Parameters
Parameter
Type
Required
Description
booking
integer
Required
Path. The booking's id.
action
string
Required
confirm, decline, cancel, completed or no_show. A booking's actions list says which apply now.
reason
string
Optional
Optional, with decline or cancel. Up to 250 characters.
scope
string
Optional
later, with cancel: this booking and the later ones of its series.
app_off: the app is switched off for the workspace. A workspace manager can switch it on in Apps.
403
The token is valid, but the user role does not grant this action.
404
No such person in this workspace.
429
The route limit for this user was hit.
Widget endpoints
The public endpoints the embed script calls from your visitors browsers. They are documented so you can debug an install or build a custom chat front end. They are not an integration API and none of them accept a token.
Base path/widget/{key}·7 endpoints
GET/widget/{key}/config
Returns the public configuration for a chatbot: branding, greeting, pre-chat form and realtime connection details.
Auth None, publicRate limit 60 requests every 1 min, per IP address
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
{
"public_key": "cv_9f2a4c7e1b6d3a8f5c0e2b4d",
"name": "Support bot",
"welcome_message": "Hi! How can I help you today?",
"human_handoff_enabled": true,
"branding": {
"primary_color": "#4f46e5",
"launcher_text": "Chat with us",
"position": "right",
"theme": "minimal",
"logo_url": null,
"avatar_url": null,
"title": "Support bot",
"subtitle": "We typically reply in a few seconds",
"layout": "classic",
"launcher": "bubble",
"quick_replies": [],
"agent_avatars": [],
"composer_style": "full",
"sound": true,
"screen_share": false,
"copilot": false,
"auto_actions": true,
"copilot_use_profile": false,
"copilot_autostart": true,
"live_visitors": true,
"behavior_tracking": false,
"recommend_mode": "reactive",
"proactive": {
"enabled": false,
"message": "Hi! Anything I can help you find?",
"delay": 20,
"scroll": 0,
"exit_intent": false,
"url_contains": "",
"frequency": "session"
}
},
"powered_by": "MagizAI",
"powered_by_url": "https://magiz.ai",
"pre_chat_form": {
"enabled": true,
"greeting": "Before we start, please introduce yourself.",
"fields": {
"name": { "enabled": true, "required": false },
"email": { "enabled": true, "required": true },
"phone": { "enabled": false, "required": false }
}
},
"realtime": {
"key": "reverbappkey",
"host": "realtime.acme.test",
"port": 443,
"scheme": "https"
}
}
Errors
Status
What causes it
404
No active chatbot has that public key. An inactive chatbot answers the same way.
429
The route limit for this IP address was hit.
POST/widget/{key}/boot
Opens or resumes a visitor conversation and returns the transcript so far.
Auth None, publicRate limit 30 requests every 1 min, per IP address
Pass a conversation_id to resume. If it does not match the visitor id you send, or that conversation was closed, a new one is opened rather than the call failing. The identity field says what this request proved about who is asking. Its state is one of verified, staff_view, expired, refused, client_area_no_client, staff_session, no_account, account_closed, module_error, not_configured, sign_in_page, signed_out or absent. It carries first_name only when this request's own identity token verified for a client, never for staff view.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
conversation_id
uuid
Optional
An existing conversation to continue. Leave it out to start a new one.
meta
object
Optional
Context about the visitor and page. meta.visitor may carry name, email and phone from your pre-chat form.
identity
string
Optional
A signed identity token from your own backend. It is verified before anything in it is trusted. Up to 4000 characters. A token whose sub or external_id is "0" is refused. A signed-in conversation continues without a token for 15 minutes after the last one that verified. After that it is set aside and a new conversation starts, and the same person's next valid token brings it back within 24 hours.
signed_out
boolean
Optional
Send true when your page knows that nobody is signed in. If the conversation belongs to someone who signed in earlier, it is closed and a new one starts, so the next person at a shared computer never sees it.
identity_state
object
Optional
Why the page has no identity token, in codes only: {"v":1,"status":"client|staff|none","reason":"..."}. The reason is one of signed_out, staff, staff_view, no_account, account_closed, module_error or not_configured. It is used to word replies and never grants access to anything. Anything outside this shape is ignored.
{
"conversation_id": "6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91",
"status": "bot",
"handoff_state": "bot_active",
"wait_remaining": null,
"identity": { "state": "absent" },
"messages": [
{
"id": 33126,
"role": "visitor",
"content": "I still have not received the replacement.",
"at": "2026-08-08T10:41:04+00:00"
},
{
"id": 33127,
"role": "assistant",
"content": "Let me check that order for you.",
"at": "2026-08-08T10:41:07+00:00"
}
]
}
Errors
Status
What causes it
404
No active chatbot has that public key. An inactive chatbot answers the same way.
422
A field failed validation. The errors object names it.
429
The route limit for this IP address was hit.
POST/widget/{key}/stream
Sends a visitor message and streams the AI reply back as Server-Sent Events.
Auth None, publicRate limit 20 requests every 1 min, per IP address
The response is a text/event-stream, not JSON. Events arrive in order: meta first, then any number of delta events, optionally recommendations, and finally done or error. A rejection before streaming starts is a normal JSON error response. The meta event also carries identity, the same object boot returns.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
message
string
Required
The visitor message. Up to 4000 characters, and it cannot be only whitespace.
conversation_id
uuid
Optional
An existing conversation to continue. Leave it out to start a new one.
meta
object
Optional
Context about the visitor and page. meta.visitor may carry name, email and phone from your pre-chat form.
identity
string
Optional
A signed identity token from your own backend. It is verified before anything in it is trusted. Up to 4000 characters. A token whose sub or external_id is "0" is refused. A signed-in conversation continues without a token for 15 minutes after the last one that verified. After that it is set aside and a new conversation starts, and the same person's next valid token brings it back within 24 hours.
signed_out
boolean
Optional
Send true when your page knows that nobody is signed in. If the conversation belongs to someone who signed in earlier, it is closed and a new one starts, so the next person at a shared computer never sees it.
identity_state
object
Optional
Why the page has no identity token, in codes only: {"v":1,"status":"client|staff|none","reason":"..."}. The reason is one of signed_out, staff, staff_view, no_account, account_closed, module_error or not_configured. It is used to word replies and never grants access to anything. Anything outside this shape is ignored.
Request
curl -N -X POST https://magiz.ai/widget/cv_9f2a4c7e1b6d3a8f5c0e2b4d/stream \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"visitor_id": "v_2c9a71b4e5",
"conversation_id": "6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91",
"message": "Where is my replacement?"
}'
Response
event: meta
data: {"conversation_id":"6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91","visitor_message_id":33128,"status":"bot","identity":{"state":"absent"}}
event: delta
data: {"delta":"Your replacement "}
event: delta
data: {"delta":"was posted this morning."}
event: recommendations
data: {"products":[{"id":41,"name":"Express delivery","price":"AED 25.00","currency":"AED","url":"https://acme.test/express","buy_url":"https://acme.test/express","image_url":null,"description":"Next-working-day delivery across the UAE.","in_stock":true}]}
event: done
data: {"message_id":33129,"source":"ai","sources":[{"title":"Shipping policy","url":"https://acme.test/shipping"}]}
Errors
Status
What causes it
404
No active chatbot has that public key. An inactive chatbot answers the same way.
422
The message was only whitespace.
422
A field failed validation. The errors object names it.
429
The route limit for this IP address was hit.
POST/widget/{key}/chat/reset
Closes the visitor current conversation so the widget can start a clean one.
Auth None, publicRate limit 30 requests every 1 min, per IP address
Idempotent and ownership-checked. It closes the conversation on the server; the widget then boots a fresh one, which is what clears the context.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
conversation_id
uuid
Required
The conversation to close. It must belong to the visitor id you send.
Polls a conversation for new messages, including replies from a human agent.
Auth None, publicRate limit 60 requests every 1 min, per IP address
Pass after with the highest message id you already hold to fetch only what is new. A signed-in conversation answers {"reauth": true, "messages": []} once its last verified token is more than 15 minutes old. Call boot again with the page's identity token to carry on.
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
conversationId
uuid
Required
Path. The conversation public id, a UUID.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
after
integer
Optional
Query. Return only messages with an id greater than this.
{
"status": "agent",
"handoff_state": "human_connected",
"wait_remaining": null,
"messages": [
{
"id": 33131,
"role": "agent",
"content": "I have opened a claim with the courier and posted a replacement today.",
"at": "2026-08-08T10:47:20+00:00"
}
]
}
Errors
Status
What causes it
404
No conversation with that id for this chatbot and visitor id.
422
A field failed validation. The errors object names it.
Records a satisfaction score for the whole conversation.
Auth None, publicRate limit 10 requests every 1 min, per IP address
Parameters
Parameter
Type
Required
Description
key
string
Required
Path. The chatbot public key, the same value used in the embed snippet.
conversationId
uuid
Required
Path. The conversation public id, a UUID.
visitor_id
string
Required
The id your widget assigns to a visitor and keeps for the session. Up to 100 characters.
score
integer
Required
Satisfaction score from 1 to 5.
comment
string
Optional
Free-text comment. Up to 2000 characters.
Request
curl -X POST https://magiz.ai/widget/cv_9f2a4c7e1b6d3a8f5c0e2b4d/conversations/6f0f1d84-2b6a-4a51-9f0e-5c2b3a7d8e91/csat \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"visitor_id": "v_2c9a71b4e5",
"score": 5,
"comment": "Sorted in two minutes."
}'
Response
{
"ok": true
}
Errors
Status
What causes it
404
No conversation with that id for this chatbot and visitor id.
422
A field failed validation. The errors object names it.
429
The route limit for this IP address was hit.
Widget endpoints not documented here
These exist and the embed script uses them, but they are internal to the widget rather than something to call yourself. They are listed so you know they were left out on purpose.
Method
Endpoint
Why
POST
/widget/{key}/upload
Multipart file upload, bound to the widget attachment flow and its storage rules.
POST
/widget/{key}/request-human
Offline handoff intake. It sends email and WhatsApp alerts to the workspace, so it stays behind the widget own flow.
POST
/widget/{key}/conversions
Conversion beacon for the reporting pipeline, meaningful only alongside the widget tracking script.
POST
/widget/{key}/track
Behaviour beacon for page, product and cart events. Opt-in per chatbot and shaped entirely by the widget.
POST
/widget/{key}/presence
Live-visitor heartbeat. Its timing is tied to the widget beacon interval.
POST
/widget/{key}/assist
Co-pilot guidance. The payload is a snapshot of the visitor page produced by the widget.
GET
/widget/{key}/attachments/{attachment}
Streams one attachment to the visitor who owns the conversation. The address is issued by the widget and checked against that visitor.
POST
/widget/{key}/chat/reveal/{token}
Exchanges a one-time token for something the visitor asked for and nothing may keep, such as their own hosting password. Only the widget's own button calls it. A token works once, for 3 minutes, inside the signed-in conversation it was made for and only for the visitor who owns that conversation. Login details are shown only for a WHMCS install running module 1.6.0 or later. Any other request is refused (404, 410 or 422) and gets nothing.
POST
/widget/{key}/chat/verification
Says whether a confirmation code sent by email is waiting in the visitor's conversation, the masked address it went to, and how many tries are left. Only the widget's own code box calls it, for the visitor who owns the conversation. Never returns the code.
POST
/widget/{key}/chat/verification/{ref}/code
Checks one code typed in the widget's code box against the code emailed for a change. Five tries per request, then the request is closed. A right code is never stored or sent on: the widget then sends a plain message, which releases the change through the normal chat turn.
POST
/widget/{key}/chat/verification/{ref}/resend
Sends a new code by the same email channel, replacing the last one. At most three times per request, thirty seconds apart, and within the merchant's own hourly limit. Answers with the masked address only.
POST
/widget/{key}/chat/pay/{token}
The pay button under a new client's first order (WHMCS module 1.8.0). Exchanges a token for WHMCS's own one-time sign-in to the account that conversation opened, on its invoice, made at that moment. Only the widget's own button calls it, from the conversation the token was made in and for the visitor who owns it, for two hours. Any other request is refused (404, 410 or 422) and gets nothing.
Need something that is not here?
Tell us what you are building and we will point you at the right endpoint, or add one.