Skip to content
20% off + 10% more tokensSee prices

DocsCustom AI tools

Custom AI tools

Let the AI look things up in your own system: an order, a subscription, whether your game server is up. You build one HTTPS endpoint. This page is what it receives and what it sends back.

How it fits together

  1. 1

    You describe the tool

    On the dashboard, under Your AI, Custom tools: a name, when to use it, what the AI sends, what it may read back, and your address. There is no Discord command for this, and it needs Manage Server.

  2. 2

    The AI decides

    While it answers someone, the AI sees that one of your tools fits and asks for it with an input.

  3. 3

    The bot calls you

    The bot checks the input, sends one signed request to your endpoint, and hands your answer to the AI.

Custom tools come with the paid plans. How many each plan holds is on the pricing page.

The request

A POST to your address, over HTTPS on port 443. One request per use, with no retry: if it fails, the AI is told the tool is unavailable and says so.

HeaderValue
Content-Typeapplication/json
Acceptapplication/json
Accept-Encodingidentity. Answer uncompressed; a compressed answer is refused.
User-AgentAITicketBot-Tools/1 (+https://aiticketbot.com)
X-AITB-Signaturesha256=<hex>. See Verifying a request.
AuthorizationOnly if you set one on the tool. Sent exactly as you typed it.
The body
{
  "tool": "lookup_order",
  "input": { "order_id": "A-10492" },
  "context": {
    "guild_id": "1234567890123456789",
    "surface": "tickets",
    "user_id": "9876543210987654321",
    "ticket_id": "17242"
  },
  "request_id": "3f0c0f4e-8f0f-4a53-9b0d-1d2a6a2d6b0e",
  "sent_at": "2026-10-02T14:58:47Z"
}
  • input only ever holds the inputs you declared, with the types you declared. A text input is never longer than the length you set.
  • Every id is a JSON string. Discord ids are too large for a JavaScript number.
  • surface is tickets, ask, faq, widget or test. ticket_id is there only inside a ticket.
  • sent_at is UTC and ends in Z.

Who is asking

input is written by an AI from what a member typed. A member can ask it to look up an order number that is not theirs, and the AI will pass that number on.

context.user_id is the only identity to trust. The bot sets it from Discord, never the AI. Check ownership against it: does order A-10492 belong to this Discord user?

It is null for an anonymous visitor of the website chat, where there is nobody to name. A tool that returns personal data should answer {"ok": false, "error": "sign_in_required"} when it is null. That is also why a new tool starts switched off for the website chat.

On a request sent by the dashboard's Test button, surface is test and user_id is the admin who pressed it.

Verifying a request

Every tool has its own signing secret. The dashboard shows it once, when the tool is added, and again only when you make a new one. The header is an HMAC-SHA256 of the exact bytes of the request body, hex encoded.

Python
import hashlib, hmac

def verify(secret: str, raw_body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)
Node.js
import crypto from "node:crypto";

function verify(secret, rawBody, header) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Compare in constant time, and verify the raw bytes before you parse them. To refuse a replayed request, reject a sent_at older than a few minutes and a request_id you have already seen. Requests come from the bot's servers with no fixed IP list, so rely on the signature, not the source address.

The answer

Status 2xx, a JSON object, at most 8 KB, within 8 seconds.

It worked
{ "ok": true, "status": "shipped", "items": ["Gold pack"], "paid_at": "2026-09-28" }
It did not, and the AI should say so
{ "ok": false, "error": "order_not_found" }

ok and error are always read. error is cut down to a short code (lowercase letters, digits and _, 40 characters), so send a code, not a sentence.

Only the result fields you listed reach the AI

The tool's result fields are listed on the dashboard, each with a type. The bot passes those on and drops everything else. This is on purpose: whatever reaches the AI, it may say to the member, and a sentence inside a result is repeated as a fact.

Type on the dashboardWhat is passed on
One of a listThe value, if it is one of your values. Otherwise dropped.
TextOne line, at most 120 characters. Line breaks become spaces.
Whole number, NumberThe number. A value of another type is dropped.
Yes or notrue or false.
List of short textsUp to 10 texts, 60 characters each.

A value of the wrong type is dropped, not converted. Do not return text typed by other people (notes, comments, usernames) in a result field, and use “One of a list” wherever the value comes from a known set. The dashboard's Test button shows which fields of your answer were dropped.

What counts as a failure

What happenedWhat the AI is told
No answer within 8 seconds{"ok": false, "error": "timeout"}
A status outside 2xx, including any 3xx (redirects are not followed){"ok": false, "error": "unavailable"}
A body over 8 KB, not JSON, or not an object{"ok": false, "error": "unavailable"}
An address that is private, local, or does not resolve{"ok": false, "error": "unavailable"}

The AI then tells the member it could not check and, inside a ticket, offers to bring in staff.

Ten failures in a row switch the tool off. The admin who added it gets one direct message, and it stays off until it is switched back on from the dashboard. An {"ok": false} answer from your endpoint is a normal answer and does not count.

Limits

  • At most 3 tool requests in one AI reply. The AI is told the rest could not run.
  • 30 requests a minute per server, across all of its tools.
  • Tools are used while the AI answers people, never while it is being trained.
  • In a reply where it used a tool, the AI does not close the ticket or blacklist the member. It can still bring in staff, and it can close on a later reply.