Skip to content
Browse the docs REST API

REST API

The REST API lets your own software ask your experts questions over HTTP. Your support portal, an internal tool or a script sends a message, and the expert's answer comes back as JSON. This page is for the person who builds that, and for the admin who creates the key they will use.

It needs the Professional plan. Only a company admin can create or revoke keys.

This is not the same thing as Provider. Provider stores a key that Aimy sends to an AI company on your behalf. A REST API key goes the other way: your software sends it to Aimy. See Provider: using your own AI key.

Create a key

  1. Go to Integrations under CONNECT and choose REST API.
  2. Choose New API key.
  3. Type a Name you will recognise later, such as Support portal.
  4. Under Experts, decide which experts the key may use. All experts is ticked to begin with, and it also covers any expert you create later. Untick it to tick only the ones you want. Each expert shows its number, such as #170, which is the chatbot_id you send in a request. Only experts that are switched on are listed.
  5. Set the Rate limit, in requests per minute. It starts at 60 and can be anything from 1 to 6000.
  6. Choose Create key.

A window called Copy your API key then shows the key once. Choose Copy, store the key somewhere safe, and choose I've saved it. Aimy keeps only a fingerprint of the key, so nobody can read it back later, including us. If you lose a key, revoke it and create another.

Give each key only the experts it needs. A key limited to one expert can never be pointed at another, even if the code that uses it changes.

The keys table

The page lists every key in a table, and the Search keys box above it filters by name, key start or expert. The columns are:

ColumnWhat it shows
NameThe name you gave the key.
KeyThe first few characters, so you can tell keys apart.
ExpertAll experts, or the experts the key may use.
LimitThe rate limit, in requests per minute.
Calls (30d)How many requests the key made in the last 30 days.
Last usedThe date and time of its latest request, or Never.
StatusActive or Revoked.

Revoke a key

Choose Revoke on the key's row, then Revoke key to confirm. Anything still using that key starts failing straight away, and it cannot be undone. Revoked keys stay in the table so you keep the history.

Base URL and authentication

Every address on this page starts with:

https://aimy.com.my/expert

Send the key as a Bearer token on every request:

Authorization: Bearer aimy_sk_your_key_here

If your tool cannot set that header, send the key in an X-API-Key header instead:

X-API-Key: aimy_sk_your_key_here

A key that is missing, wrong or revoked gets the same 401 answer in every case, so the response never tells anyone which one it was.

Call the API from your server, never from a web page. Anything a browser can send, a visitor can read out of the page. Aimy sends no cross origin headers on purpose, so browsers refuse the call. Keep the key in an environment variable or a secrets manager, and never in a public repository or a mobile app.

Ask a question

POST /api/v1/chat

curl -X POST https://aimy.com.my/expert/api/v1/chat \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "message": "What is the refund policy?",
        "chatbot_id": 170
      }'

The request body is JSON with these fields.

FieldRequiredWhat it does
messageYesThe question. Up to 8000 characters.
chatbot_idUsuallyWhich expert should answer. You can leave it out only when the key is limited to exactly one expert. Get the ids from the experts list below.
conversation_idNoLeave it out to start a new conversation. Send the same value again to carry on with it.
user_nameNoThe name of the person asking, up to 100 characters. History and the handoff email show it.
user_emailNoTheir email address, up to 100 characters.
user_contactNoA phone number or other contact detail, up to 100 characters.
request_handoffNoSet to true to ask for a person to take over the conversation. See Handing a chat to a person.

A successful answer looks like this.

{
  "success": true,
  "answer": "Refunds are available within 30 days ...",
  "conversation_id": "8f2c1a7b9d0e4f6a1b2c3d4e",
  "chatbot": { "id": 170, "name": "Support Expert" },
  "citations": [ { "pdf_id": 31, "name": "policy.pdf", "page": 4, "score": 0.81, "snippet": "...", "kind": "retrieval", "url": null } ],
  "sources":   [ { "document": "policy.pdf", "page": 4, "preview": "...", "score": 0.81 } ]
}

citations and sources follow the Show sources switch on the expert. With the switch off, both come back as empty lists. They are always present, so your code does not need to check whether they exist.

A citation object has these fields.

FieldWhat it holds
pdf_idThe id of the document. It is 0 for a web result.
nameThe document name, or the page title for a web result.
pageThe page number, or null when the source has no pages, which is always so for a web result.
scoreHow closely the passage matched the question, or null.
snippetThe passage the answer drew on.
kindretrieval for a passage from your documents, web for a web search result, or analysis when the expert worked something out from a spreadsheet.
urlThe address of the page for a web result, otherwise null.

An answer has at most 6 citations. An answer that is a refusal, such as "I don't have that information", usually comes back with none.

The expert answers with the same documents and settings it uses everywhere else.

Keep a conversation going

The first answer returns a conversation_id. Send it back with the next message and the expert remembers what was said.

# First call: no conversation_id
{"message": "Do you ship to Sabah?", "chatbot_id": 170}
  -> {"answer": "Yes ...", "conversation_id": "8f2c1a7b9d0e4f6a1b2c3d4e"}

# Follow up: same conversation_id
{"message": "How long does it take?",
 "chatbot_id": 170,
 "conversation_id": "8f2c1a7b9d0e4f6a1b2c3d4e"}

You can also make up your own id, such as a ticket number. It must be 1 to 64 characters, using letters, digits, _ or -. A new id starts a fresh conversation that knows nothing about the others.

The conversations appear in History, titled with the key's name unless you send user_name. See History.

List your experts

GET /api/v1/experts

curl https://aimy.com.my/expert/api/v1/experts \
  -H "Authorization: Bearer YOUR_KEY"
{
  "success": true,
  "experts": [
    { "id": 170, "name": "Support Expert", "description": "...", "active": true, "documents": 12 }
  ],
  "pinned": false
}

The list holds only the experts this key may use. It includes experts that are switched off, shown with "active": false. Asking a question of one of those returns 409 with the code chatbot_inactive. pinned is true when the key is limited to exactly one expert, which means you can leave chatbot_id out when you ask questions.

documents is a count of every processed document that belongs to the expert's owner. It is not limited to the documents attached to that expert, so two experts with the same owner show the same number.

The older address /api/v1/chatbots still works and returns the same list, under the name chatbots instead of experts.

When a person takes over

If someone on your team has taken over a conversation, the expert stops answering it. /api/v1/chat then returns "live_agent": true with an empty answer. That is not an error, so do not retry the message. Ask for the person's replies instead:

GET /api/v1/messages?conversation_id=8f2c1a7b9d0e4f6a1b2c3d4e&chatbot_id=170&after_id=0

curl "https://aimy.com.my/expert/api/v1/messages?conversation_id=8f2c1a7b9d0e4f6a1b2c3d4e&chatbot_id=170" \
  -H "Authorization: Bearer YOUR_KEY"
{
  "success": true,
  "live_agent": true,
  "status": "assigned",
  "messages": [
    { "id": 41, "sender": "agent", "body": "Hi, I can help with that.", "created_at": "2026-10-12 10:15:02" }
  ],
  "last_id": 41
}

conversation_id is required. chatbot_id follows the same rule as for chat. Keep the last_id you get back and send it as after_id on the next call, so you only receive new messages. sender is agent for a person and system for a notice. Your own messages are not echoed back. status is open while the conversation waits for someone and assigned once a person has it.

When the person finishes, live_agent becomes false with no messages, and the expert answers again from the next message.

Errors

Every failure has the same shape. Branch on code. The message is for people and may be reworded.

{
  "success": false,
  "error": { "code": "rate_limited", "message": "Too many requests. This key allows 60 per minute.", "retry_after": 60 }
}
StatusCodeMeaning
400invalid_requestA field is missing or malformed. The message says which.
400chat_failedThe expert could not answer this message.
401unauthorizedThe key is missing, wrong or revoked.
402quota_exceededThe account has used up its allowance for the month, or has no active plan.
403forbidden_planThe account's plan does not include the REST API.
403chatbot_not_allowedThis key may not use that expert.
404chatbot_not_foundNo such expert in your company.
405method_not_allowedWrong HTTP method. Chat is POST. Experts and messages are GET.
409chatbot_inactiveThe expert is switched off. It still appears in the experts list, marked inactive.
429rate_limitedToo many requests. Wait for Retry-After.
500internal_errorThe request could not be completed.
502upstream_errorThe expert service did not return a valid response. Try again.
503busyThe service is under load. Try again shortly.

Rate limits

Each key has its own limit, counted over a window of 60 seconds. Requests that fail still count, as long as the key was accepted. Once the key is accepted and the plan check passes, every response carries two headers:

  • X-RateLimit-Limit is the key's limit.
  • X-RateLimit-Remaining is how many requests are left in the current window.

The headers are missing from 405, 401 and 403 forbidden_plan answers, because those are refused before the limit is checked.

A 429 also carries a Retry-After header and a retry_after field in the error. Both are always 60 seconds. The rate limit is separate from your plan's monthly allowance, so you can be inside the first and still get quota_exceeded.

Usage and history

Answers count toward your plan's allowance like any other channel, and Usage lists API as its own channel. The keys table shows each key's last use and its calls over the last 30 days. Aimy keeps the log of individual requests for 30 days.