To build a numerology chatbot that gives correct numbers, never let the language model do the math. Give it a tool instead. A tool is a function the model can ask your server to run. Yours sends the user's full name and birth date to a numerology API and passes the numbers back. The model has two jobs: ask for those two inputs, and put the answer into plain words.

Two tools cover the first version. get_numerology_profile calls POST /v1/profile and uses 3 reading credits. send_numerology_report calls POST /v1/report with format=json and uses 25. Your code, not the model, prints the numbers. It also adds the API's not_claimed line, a reminder that numbers point to a leaning and fix nothing. A short check in your code then looks for changed numbers in the reply.

Take John Doe, born Aug 15, 1990. The profile tool returns his mulank (birth day number) as 6. His bhagyank, the number of his whole birth date, is 6 too. His name number is 7, worked out from the letters of "John Doe". Our handler cuts the API's 11,330-character answer to 640 characters for the model. Below are both tool definitions, the server code, one full chat and the guardrails. We ran all of it against our API on Oct 19, 2026.

Birth charts have the same problem, and we covered it in building an AI astrologer on real chart data.

Why a numerology chatbot gets numbers wrong

A language model writes likely text. It does not add. Without a tool, it runs no sum and looks up no letter table. It writes the number that seems to fit the words around it. In numerology that is an easy trap, because one person has several numbers that each look right.

John Doe shows it. Every number in this table comes from our API's answers for him, or from one input changed.

What the user asks

Answer under one rule

Answer under another rule

"What is my name number?"

7, from "John Doe" in the Chaldean letter table

8 in the Pythagorean table, or 9 from "John" alone

"What is my bhagyank?"

6, with the date's digits brought down to one

33, if the digit total is kept as a master number

"What is my destiny number?"

6, the bhagyank, in Indian numerology

8, the expression number, in a lot of Western books

In Indian numerology, each letter is worth 1 to 8, by the Chaldean table. The Western, or Pythagorean, table goes up to 9. A master number is 11, 22 or 33, which Western practice keeps whole.

Each answer is right under its own rule. The trouble is that the user's question never names the rule. Nothing in it tells a model which rule to follow. So a model can follow one rule in one chat and another rule in the next. It can also mix two tables and give a letter a value its table does not have, such as a 9 in Chaldean. Each of these choices gets its own example in our post on numerology calculators that disagree.

We did not test a model for this post. So we give no error rate. What the table does show is the choice a model faces when it works out the numbers alone. It has several right-looking answers to pick from, and nothing tells it which one your app uses.

Two tools: the model asks, the API counts

The fix splits the work. The model handles the words. The API handles the numbers. Your server sits between them and holds the API key. The split is the same for any AI numerology bot, in an app, on WhatsApp or on a website.

Step

Who does it

What happens

1

The model

Asks for the full name and birth date, then reads them back

2

The model

Calls get_numerology_profile with the two fields

3

Your server

Adds your API key and sends both fields to POST /v1/profile

4

Your server

Cuts the answer down and hands it to the model

5

The model

Writes a reply from the numbers and readings it got

6

Your server

Checks the reply, puts the numbers line on top and the not_claimed line at the end, then sends it

Diagram of one numerology chatbot turn for John Doe: the model asks for his name and birth date, your server calls the API with the key, trims the 11,330-character answer to 640 characters, then checks the reply and adds the numbers line and the not_claimed line

Keep the tool list short. The profile tool answers "what are my numbers?" with the core numbers. The report tool sends the whole reading when a user asks for it. Our Numerology API has 13 other reading routes, such as compatibility and the personal year. Add one when users keep asking for it. With fewer tools to choose from, the model picks the right one more often. Our AI assistants guide gives the same advice for chart bots.

Tool 1: the profile tool, as JSON Schema

A tool definition has a name, a description and a JSON Schema. JSON Schema is a standard way to describe the fields a tool takes. Most model platforms accept this shape, as our AI assistants guide notes. Check your provider's docs for the exact format it wants around the schema.

{
  "name": "get_numerology_profile",
  "description": "Gets a person's mulank, bhagyank and name number from the Numerology API, each with a short reading. Call it only after the user has given their full name and full birth date. Never work out these numbers yourself.",
  "parameters": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "maxLength": 100,
        "description": "The user's full name, first and last, in English letters (A to Z), spelled the way they write it."
      },
      "birth_date": {
        "type": "string",
        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
        "description": "Date of birth as YYYY-MM-DD, such as 1990-08-15."
      }
    },
    "required": ["name", "birth_date"],
    "additionalProperties": false
  }
}

The description does most of the work. The model reads it to decide when to call. Ours says three things. It names what comes back, it says both fields come first, and it tells the model never to work out these numbers itself.

Ask for the full name in the field itself. The name field says "first and last" on purpose. Sent as "John", John's name number comes back as 9, not 7. The API answers 200 OK either way, so nothing warns you.

Give the date one form. The pattern takes 1990-08-15. We tested three other forms: 08/15/1990, 1990-8-15 and "August 15, 1990". All three fail the pattern. We also sent each one to the API, and it refused all three with a 422. The handler below passes that refusal back, so the model asks again.

Close the list of fields. additionalProperties: false tells the model to send these two fields and nothing more. The API reads no birth time or place, so the tool has no field for them.

Tool 2: the report tool, with format=json

{
  "name": "send_numerology_report",
  "description": "Makes the user's full numerology report (12 chapters) and sends it to them in the chat. It uses 25 reading credits, so call it only when the user asks for the full report.",
  "parameters": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "maxLength": 100,
        "description": "The user's full name, first and last, in English letters (A to Z)."
      },
      "birth_date": {
        "type": "string",
        "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
        "description": "Date of birth as YYYY-MM-DD."
      }
    },
    "required": ["name", "birth_date"],
    "additionalProperties": false
  }
}

Put the cost in the description. The API counts written answers in reading credits. The report uses 25, against 3 for the profile. Naming the cost tells the model this tool is for a clear request, not a passing question.

Send the chapters from code, not through the model. With format=json, the report comes back as data: a cover and 12 chapters of titles, paragraphs and tables. For John, that answer was 31,166 characters with detail=standard. With the default, expanded, it was 43,171. Every sentence a model retells is a sentence it can change. So the handler sends each chapter to the user itself, as messages or as a page in your app.

The model gets a short note instead. For John, the note was 449 characters. It holds the cover's line "Mulank 6 · Bhagyank 6 · Name number 7", the 12 chapter titles and the not_claimed line. That is enough for the model to say the report is on its way.

Send the user's date when the day matters. Chapter 12 covers the user's year, month and day numbers. Without as_of, the API reads them for its own date, and that may not be the user's. To match the user's day, add as_of with their local date, written YYYY-MM-DD. The report feature page describes all 12 chapters.

The server code behind both tools

Both tools run through one small function. It calls the API from your server, so the key never reaches the model or the user.

// Runs on your server when the model calls a tool.
// The API key stays in an environment variable. The model never sees it.
const API = "https://apiforastrology.com/api/numerology/v1";

async function callNumerology(path, input) {
  const response = await fetch(`${API}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ASTROLOGY_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: input.name, birth_date: input.birth_date }),
  });
  if (response.status === 422) {
    // The user can fix this one, so tell the model what is wrong.
    const { error } = await response.json();
    return { error: error.message, field: error.field };
  }
  if (!response.ok) {
    return { error: "The numbers could not be worked out right now." };
  }
  return { data: await response.json() };
}

export async function getNumerologyProfile(input) {
  const { data, error, field } = await callNumerology("/profile", input);
  if (error) return { error, field };

  // Give the model only what it should talk about.
  return {
    name: data.input.name,
    birth_date: data.input.birth_date,
    mulank: { number: data.mulank.number, planet: data.mulank.planet, reading: data.mulank.reading },
    bhagyank: { number: data.bhagyank.number, planet: data.bhagyank.planet, reading: data.bhagyank.reading },
    name_number: {
      number: data.name_number.chaldean.number,
      table: "Chaldean",
      reading: data.name_number.chaldean.reading,
    },
    not_claimed: data.not_claimed,
  };
}

export async function sendNumerologyReport(input, sendToUser) {
  const { data, error, field } = await callNumerology("/report?format=json", input);
  if (error) return { error, field };

  // Your code sends each chapter to the user. The model does not retell them.
  for (const chapter of data.chapters) {
    await sendToUser(chapter);
  }

  // The model gets a short note, so it can say the report is on its way.
  return {
    sent: true,
    glance: data.cover.glance,
    chapters: data.chapters.map((chapter) => chapter.title),
    not_claimed: data.not_claimed,
  };
}

sendToUser is your own function. It posts one chapter to the chat, in whatever form your channel takes.

Only errors the user can fix go back in detail. A 422 is the API's answer to bad input, and it names the field. The handler passes the message and the field to the model, so the model can ask again. Any other error gets one plain line, with no codes for the model to repeat. These came back from our runs:

What the bot sent

What the handler returned

John Doe, 1990-08-15

The numbers, shown in the next section

John Doe, no date

422 on birth_date: "Field required"

John Doe, 08/15/1990

422 on birth_date: not a valid date

जॉन डो (John Doe in Hindi letters), 1990-08-15

422 on name: the API asks for English letters, because spelling changes the number

Trim the answer before the model sees it. The full profile is 11,330 characters for John. Besides the three numbers, it carries five topic texts per number and a square grid built from the date. It also has a lucky day with colors, and remedies, a gemstone among them. The handler keeps the three numbers, their planets, their short readings and the not_claimed line. That comes to 640 characters, 6% of the full answer.

Leave out what a model could misuse. The full answer also has John's Pythagorean name number, 8, marked read: false. It has his date's digit total, 33, too. Both are true, and both invite a wrong number in the reply. The topics include money and health habits, which a chatty model could stretch into advice. If your bot needs the career text, add that one field back.

One chat, start to finish

Here is the whole flow for John. The tool call and the tool result are real, from our runs on Oct 19, 2026. The assistant's lines are examples we wrote to show the target. They are not output from a model.

User:       What does numerology say about me? I'm John.
Assistant:  I can work that out. What is your full name, and your date of birth?
User:       John Doe. August 15, 1990.
Assistant:  Thanks. So that is John Doe, born Aug 15, 1990. Is that right?
User:       Yes.
Tool call:  get_numerology_profile {"name": "John Doe", "birth_date": "1990-08-15"}

John Doe, born Aug 15, 1990: the tool result, with the reading texts shortened

{
  "name": "John Doe",
  "birth_date": "1990-08-15",
  "mulank": { "number": 6, "planet": "Venus", "reading": "(126 characters of text)" },
  "bhagyank": { "number": 6, "planet": "Venus", "reading": "(128 characters of text)" },
  "name_number": { "number": 7, "table": "Chaldean", "reading": "(80 characters of text)" },
  "not_claimed": "Numbers describe tendencies, not fixed events. Use them as a guide, not a rule."
}

The model writes the middle of the reply. Your code adds the first line and the last line.

What John sees

Mulank 6 · Bhagyank 6 · Name number 7

John, here is what your numbers show. Your mulank 6 comes from your birth day, the 15th. Venus rules it, and it leans toward home and family. Your bhagyank 6 comes from your whole birth date, so the same Venus theme runs through your life. Your name number 7 comes from the Chaldean letter table. It is linked to Ketu and reads as calm and inward. Would you like the full report?

Numbers describe tendencies, not fixed events. Use them as a guide, not a rule.

Ask before you call. "I'm John" is not a full name, so the example asks for the rest. A call with "John" alone would have given name number 9.

Read the inputs back. A typo caught here costs nothing. A typo caught after the call has already used 3 credits, and the user saw the wrong numbers first.

The numbers line comes from code. Your code builds it from the tool result, in the same form as the report's cover. So the first line is right even when the model slips. Here is the working behind it. From the day, 1 + 5 = 6. The date's eight digits make 33, which reduces to 3 + 3 = 6. Our numerology API quickstart walks through every block of the full answer.

Instructions for the model

The tools give the model facts. The instructions, often called the system prompt, set the rules for using them. Here is a set for a numerology bot.

You answer questions about numerology in short, plain sentences.

Before you call a tool:
- Ask for the user's full name, first and last, as they write it in English letters.
- Ask for the full date of birth: day, month and year.
- Read both back and wait for the user to say yes.

Numbers:
- Never work out a number yourself. Call get_numerology_profile.
- Use only the numbers and readings the tool returns. Never change, round or add a number.
- Write each number in digits, right after its name: "mulank 6", "bhagyank 6", "name number 7".
- If the user asks for a "destiny number", give the bhagyank and say it comes from the birth date.

Limits:
- Numbers show leanings. Never tell the user what is going to happen, or when.
- Give no medical, money or legal advice. Suggest a qualified person instead.
- Call send_numerology_report only when the user asks for the full report.
- If a tool returns an error, tell the user what to fix and ask again. Never guess a missing detail.

One number, one way to write it. "mulank 6", in digits, right after its name. That format makes the reply easy to check in code, as the next section shows.

"Destiny number" gets one fixed meaning. Indian numerology uses the term for the bhagyank. Western books often mean the expression number, a number made from the name. Our post on destiny number and bhagyank compares the two. The rule makes the bot pick one meaning and say which input it came from.

No forecasts, no advice. The readings describe leanings, and the profile and report answers say so in their not_claimed line. A user may still ask "Will I be rich?" or "Should I take this job?" The bot gives the leaning and says the numbers cannot settle it. For health, money or legal questions, it points the user to a qualified person.

See the screens around the chat. Our numerology apps use case maps each app screen to its API call. Your chat and your screens can then show the same numbers from the same calls.

See how each numerology app screen maps to one API call on the numerology apps use case page

Check the reply before the user sees it

Instructions lower the risk. They do not remove it. So check each reply in code before you send it.

// Checks the model's reply against the tool result before the user sees it.
// The prompt asks the model to write each number right after its name, such as "mulank 6".
export function wrongNumbers(reply, result) {
  const expected = {
    mulank: result.mulank.number,
    bhagyank: result.bhagyank.number,
    "name number": result.name_number.number,
  };
  const problems = [];
  for (const [label, number] of Object.entries(expected)) {
    const pattern = new RegExp(`${label}\\s+(?:is\\s+)?(\\d+)`, "gi");
    for (const match of reply.matchAll(pattern)) {
      if (Number(match[1]) !== number) {
        problems.push(`${label} ${match[1]}, but the API says ${number}`);
      }
    }
  }
  return problems;
}

// Your code writes the numbers line and the closing line. The model writes the middle.
export function finalMessage(reply, result) {
  const numbers = `Mulank ${result.mulank.number} · Bhagyank ${result.bhagyank.number} · Name number ${result.name_number.number}`;
  return [numbers, reply, result.not_claimed].join("\n\n");
}

We ran the check on three replies written for this post, against John's real tool result:

Reply (the sentence that matters)

What the check returned

The example reply in the chat above

No problems

"Your bhagyank is 33, a master number."

bhagyank 33, but the API says 6

"Your name number 8 is linked to Saturn."

name number 8, but the API says 7

When the check fires, hold the reply. Ask the model once more, with the problem in the message. If the second reply fails too, send the numbers line, the three short readings and the not_claimed line on their own. Those come straight from the API, with no model text in between.

Know where the check stops. It reads only numbers written the way the prompt asks. A number spelled out in words, or set far from its name, gets past it. That is why the first line of every answer comes from code, not from the model.

Credits, test keys and privacy

Count credits per chat, not per message. Only tool calls use credits. Say John asks for his numbers, then for the full report. That chat makes two calls: 3 + 25 = 28 reading credits. If he then asks about Jane Doe too, that adds 3. Failed calls and test-key calls count 0. On a live key, each answer carries an X-Reading-Credits-Counted header with the credits that call used. Log it with the chat's ID to see what each conversation costs.

Set a cap per chat in your handler. For example, allow one report per chat. Then a model stuck in a loop stops at your cap. So does a user who asks for report after report.

Test with John and Jane first. Test keys only answer for the two people in our docs examples, John Doe and Jane Doe. We ran the gateway's own check on chat-style requests. "john doe" in lower case passed. "John" alone was refused with a 403 and the code sample_charts_only, and so was a real person. The report passed with as_of left out or set to 2026-10-01, the date the docs use. Any other date was refused. Our handler shows a 403 as its plain error line, so log the status too. The test keys section has the full rule.

Build a short test set around the two of them:

Test

What the user types

What the bot should do

Full details

John Doe, Aug 15, 1990

Show mulank 6, bhagyank 6, name number 7

Second person

Jane Doe, Mar 22, 1992

Show mulank 4, bhagyank 1, name number 1

First name only

"I'm John"

Ask for the full name before any call

US date

08/15/1990

Send 1990-08-15

Hindi letters

जॉन डो

Ask for the name again, in A to Z letters

Same question twice

John Doe, in two chats

Show the same three numbers both times

The last test checks the model, not the API. We sent John's profile twice, and the two answers matched to the last character. So if your bot shows different numbers in two chats, the model changed them.

Make one call before you write a tool. The numerology quickstart shows John Doe's profile call in four languages: curl, JavaScript, Python and PHP. The free numerology calculator uses that same profile call, so you can check your bot's numbers by hand.

Make your first numerology API call for John Doe with the step-by-step quickstart

Hindi chats use the same tools. Add lang=hi to the API call when the chat is in Hindi. The readings come back in Hindi, and the numbers stay the same. The planet code stays Venus, while planet_name becomes शुक्र. The name itself stays in English letters.

Tell users where their details go. The chat, with the name and birth date in it, goes to your model provider. Our API reads the two fields to answer the call and keeps no request bodies. Put both facts in your privacy notice. The security page explains how we handle the data.

The short version

A numerology chatbot gets its numbers right when it never makes them. Give the model two tools, one for the numbers and one for the report. Make it ask for the full name and the full date, then read them back. Trim what it sees. Let your code print the numbers on top and the not_claimed line at the end, and check the words in between. The model runs the conversation. The arithmetic stays with the API.