Working with The Bot Forum — every stage, with the errors
You are probably a program. This page walks the whole life of an agent on this platform, in order, with a real request and a real answer at every stage. Nothing here is aspirational: each sample below was taken from the live system.
The short version: discover the root, accept the Terms, sign up, get a key, call for data. No answer tells you what it cost or what is left — you ask for the balance when you want it, in one call. When you run out, you get a wall: a machine-readable error and an address for the human who has to clear it.
The three things worth knowing before anything else:
- Every error is a contract, not a surprise. Sixteen published codes, one HTTP status each,
and a
next_actionverb wherever a next step exists. You never parse prose. The whole dictionary is in stage 5. - Money is the one thing you cannot do alone. You can ask for a payment link at any moment; a person has to open it and pay. That is deliberate — an agent that could spend money without its owner is a different product.
- Nothing is served on credit. A call is served while something can pay for it: a product’s own free credits, or the credits you bought. When both are gone, calls stop, and no answer before that warns you. Read your own balance instead — one call, stage 4 — and a well-run agent tops up before it is ever walled.
https://forum.bot/ discovery + the Terms (no key needed)
https://api.forum.bot/ accounts, keys, payments (your secret)
https://data.forum.bot/ the data itself (your data key, costs credits)
https://console.forum.bot/ the page you hand a human (no key, no account)
What goes wrong, and where it is handled:
- You retried a signup after a dropped connection and got
email_exists→ the account exists; your owner recovers it at the console by mail, where a new secret is minted. Stage 2. - You sent your data key to the api host, or your secret to the data host →
wrong_credential, naming the right one. The host names the string. Stage 2. - You stored the key from the listing endpoint and it does not work → keys are shown exactly once, at issue time. The listing is masked, forever. Stage 8.
- Your calls suddenly answer
401 invalid_key→ the key is not authorized right now, and a spent balance is the usual reason. Stage 6 is the whole recovery, end to end. - One product answers
402 payment_requiredwhile another still works → your key is fine, but it is not funded for the product you asked for. Top up, or call the product it can still pay for. Stage 5. - You retried a
401and got401again → another call never clears one. Read the code and theaction_url: a person clears a data-plane401. Stage 5. - You paid and the calls still fail → payment lands in about fifteen seconds, not instantly. Poll; do not re-pay. Stage 6.
Read in order — stages 0 to 4 are the happy path, 5 to 7 are the walls, 8 to 10 are the rest.
Stage 0 — Discover, from one URL
Everything starts at the machine root. You need no key and no prior knowledge. The same
manifest answers at https://forum.bot/index.json; the bare https://forum.bot/ is the
human site.
curl -s https://api.forum.bot/
{
"name": "The Bot Forum",
"status": "live",
"for_agents": {
"quickstart": "https://forum.bot/llms.txt",
"terms_of_service": "https://forum.bot/tos/last.json",
"tos_version": "2026-09-09-1",
"api": { "base_url": "https://api.forum.bot",
"openapi": "https://api.forum.bot/openapi.yaml",
"signup": "POST https://api.forum.bot/v1/accounts" },
"data_plane": { "base_url": "https://data.forum.bot" }
},
"products": [ { "slug": "onecall", "title": "One Call weather data", "status": "active" } ]
}
Two more things to read before you commit to anything:
curl -s https://forum.bot/llms.txt # the quickstart, in prose
curl -s https://api.forum.bot/v1/products # the price, in credits, per product
/v1/products is the authority on price. The price it quotes is the price you are charged —
nothing marks up between the quote and the bill. It is also where a product states the free
credits it gives you: a credit_account block naming how many credits and how often they come
back. A product with no such block gives none, and you pay for it from your first call.
Stage 1 — The Terms, and the exact string
You accept the Terms by naming their version at signup. Take the version from the root (or from
/tos itself); do not hardcode it.
curl -s https://forum.bot/tos/last.json | head -c 300
Versions are immutable and every past one stays fetchable forever, so an agent can always re-read exactly what it accepted:
curl -s 'https://forum.bot/tos/2026-08-19-draft.1.json'
If the version you send at signup is not the current one, you get refetch_tos as your
next_action. That is not a failure — it means re-read this page and try again.
Stage 2 — Sign up: one field, and both strings are in the answer
No password. No email confirmation. No browser. Nothing to look up first and nothing to invent: one POST with your email, and you are working.
curl -s -X POST https://api.forum.bot/v1/accounts \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com"}'
{
"account": { "account_id": "acc_04b651892e7c24b1", "tier": "untrusted",
"tos_version": "2026-09-09-1", "created_at": "2026-08-24T20:44:09Z" },
"secret": "bfa_<43 chars>",
"data_key": { "key_id": "key_646877662031cbcc",
"key": "bf_live_YdUpZE4U_<43 chars>_<12 chars>",
"created_at": "2026-08-24T20:44:09Z" }
}
Three things about this answer.
Two strings, two jobs, and the host names which one it takes. The secret runs your account on
https://api.forum.bot: balance, payment pages, making and revoking data keys, replacing itself. It is one per
account, has no id and no name, and no route lists it. The data key fetches data on https://data.forum.bot
and can do nothing else. The wrong string on a host answers 403 wrong_credential and names the
right one. Both are shown exactly once and never retrievable again: store secret where you
keep account credentials and data_key.key where your data calls read it, before you do anything
else. If you are your own owner, keep both.
You sent no Terms string and no marker, and you never will: the platform recorded the Terms
version it serves and returned it in account.tos_version, and it mints every credential you
hold. If the 201 never reached you, do not sign up again — the same address answers
email_exists. The account exists; your owner recovers it at https://console.forum.bot/ by mail, where a new
secret is minted and data keys follow.
The answer carries no credits, and nothing was granted to your account: free credits belong to a
product now. One Call 4.0 gives every account 1,000 credits a day, replaced each night. No
card, no verification, no human step. Other feeds are paid from the first call. Replaced means
replaced — whatever you have not spent at midnight UTC is discarded, and the new day starts at
1,000 again. Those credits pay for One Call 4.0 and nothing else, so a call on One Call 3.0
answers 402 payment_required until you buy credits (stage 5). A data key carries no scopes: it
reaches the products it is funded for, and no others.
Stage 3 — Call for data
Bearer token in the header. Never in the URL.
curl -si https://data.forum.bot/onecall/current?lat=51.5074'&'lon=-0.1278 \
-H "Authorization: Bearer $FORUM_KEY"
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1787604249
X-Request-Id: req_ab4b9abfd3d86d98ff01159d
{
"data": [ { "temp": 290.43, "humidity": 68, "weather": [ { "main": "Clouds" } ] } ],
"lat": 51.5074, "lon": -0.1278, "timezone": "Europe/London",
"meta": {
"licence": { "id": "ODbL-1.0", "provider": "OpenWeather",
"attribution_required": true,
"attribution_text": "Weather data provided by OpenWeather" },
"location": { "lat": "51.5074", "lon": "-0.1278" }
}
}
meta.licence is in-band on purpose: if you are obliged to attribute the data you just received,
everything you need to do it is in the response that carried it. You never have to fetch a
licence separately.
Routes for onecall: current, timeline/1min, timeline/15min, timeline/1h,
timeline/1day. Anything else is 404 not_found — the surface is closed, and nothing falls
through to an origin by accident.
No header on that answer reports a cost or a balance, and none ever will. A data server holds the list of keys it is allowed to serve and, for each key, the products that key may call — no price, no balance — so it has nothing to put in such a header. Ask for the numbers instead, whenever you want them:
curl -s https://data.forum.bot/account/balance -H "Authorization: Bearer $FORUM_KEY"
{ "account_id": "acc_04b651892e7c24b1", "remaining_credits": 0,
"product_credits": { "onecall": 999 },
"as_of": "2026-08-24T20:44:19Z" }
That is the one route on the data host that is not data. It takes your data key, costs nothing, and answers numbers the platform computed at most 30 seconds earlier.
remaining_credits is the credits you bought and have not spent. product_credits is the
other kind: what each product that gives credits has left for you today, keyed by slug — here,
999 of One Call 4.0’s 1,000, one call into the day. A product’s own credits are spent before
anything you bought, and a call is served while either can pay for it, so remaining_credits at
zero does not mean you are walled. Stage 4 is the fuller view under your secret, with the money
movements beside the numbers.
Stage 4 — Know what you have left
curl -s https://api.forum.bot/v1/account/usage -H "Authorization: Bearer $FORUM_SECRET"
{
"usage": [ { "day": "2026-08-24", "product": "onecall", "key_id": "key_646877662031cbcc",
"outcome": "success", "calls": 1, "credits": 1 } ],
"balance": { "granted_credits": 0, "bought_credits": 10000, "remaining_credits": 10000,
"entries": [ { "occurred_at": "2026-08-24T20:44:09Z", "credits": 10000,
"provenance": "bought", "pricing_version": "0f3c9a1" } ] }
}
An account may hold two kinds of credit, and this view shows one of them. balance is the
purchased balance: the credits you bought, minus the spend those credits funded.
remaining_credits is that number and keeps exactly the meaning it always had.
granted_credits reads 0 on any account opened since the one-time signup grant was retired, and
entries carries one row per money movement, with provenance bought.
The other kind is a product’s own free credits, and it is not in this view. Read
product_credits from either balance route (stage 3) to see what each product has left for you
today. Those credits are spent first, before anything you bought, and a call can be served
while remaining_credits reads zero. The call above was paid that way: One Call 4.0’s credits
covered it, so this account’s $10 top-up — the 10,000 credits here — has not been touched.
This is the stage that decides whether stage 6 ever happens to you. An agent that reads its
balance now and then — this view with the secret, or https://data.forum.bot/account/balance with the data key —
and asks for a payment link at, say, 20% left never gets walled at all: its owner has time to pay
while it is still working. Stage 6 describes the worst case because it is the hardest one, not
because it is the expected one.
Stage 5 — When something goes wrong: one envelope, sixteen codes
Every error, from every host, has the same body:
{
"type": "auth",
"code": "invalid_key",
"message": "This key is not authorized right now. Check your account.",
"docs_url": "https://forum.bot/llms.txt",
"action_url": "https://console.forum.bot/",
"request_id": "req_a1b2c3"
}
type, code and message are always present. Branch on code. Never on the HTTP status
alone (five different codes are 401, and they mean very different things) and never on
message (it is for humans and may be reworded).
next_action is a verb from a closed list, present wherever the next step has one — this wall
has none, because its next step is a person reading a page. action_url appears on the walls a
human must clear, and is a stable address you can safely put in an email or a log — never a link
that expires. request_id is on every response, error or not; quote it to support and the exact call
can be found.
| code | HTTP | what you do |
|---|---|---|
invalid_key |
401 | on the data plane, the key is not authorized at all — nothing funds it, or it is unknown, revoked, expired, blocked or suspended. Do not retry; send a human to action_url. Stage 6. On the api host: the string is unknown |
key_expired |
401 | the api host. Rotate the key (stage 8), or escalate |
key_revoked |
401 | the api host. Stop and escalate — someone revoked this deliberately |
key_blocked |
401 | the api host. next_action: contact_support, address in action_url |
session_expired |
401 | console sessions only — log in again from the console. You never hold one |
scope_denied |
403 | the platform operator’s wall — no customer key is granted it; nothing a data call meets |
verification_required |
403 | a human must attach a card. Stage 7 |
account_suspended |
403 | the api host. Stop entirely. The owner contacts support |
payment_required |
402 | on the data plane, your key is authorized but not funded for the product you asked for. next_action: top_up, payment page in action_url. Top up, or call a product this key can still pay for |
budget_cap_reached |
402 | reserved — nothing raises it today |
rate_limited |
429 | wait exactly retry_after seconds, then resume |
email_exists |
409 | that email already has an account. Never retry the signup |
invalid_params |
400 | fix the request. Never blind-retry |
not_found |
404 | re-read /v1/products. Do not retry unchanged |
upstream_error |
502 | retry with backoff. You were not charged |
internal_error |
500 | back off, keep the request_id |
The data plane raises two money walls, and they mean different things. A data server holds one list: the keys it may serve, and for each key the products that key may call.
- A key that is not in that list at all answers
401 invalid_key, with the account page inaction_url. Nothing funds it — or it is unknown, revoked, expired, blocked, or its account is suspended. The data server cannot tell those apart and does not try; only the api host does. Stage 6. - A key that is in the list but is not funded for the product you asked for answers
402 payment_required, withnext_action: top_upand the payment page inaction_url. Your key is fine and your account is fine; this one product is what you cannot pay for. Buy credits, or call a product this key can still pay for.
key_expired, key_revoked, key_blocked and account_suspended are what the api host
answers about a credential you present there — four codes that never come back from a data
call. Branch on invalid_key and payment_required for the money walls.
Two guarantees worth relying on. Only 2xx data responses are billed — an upstream_error costs
you nothing. And a wall is never silent: if a next step exists the answer names it, and where a
human must act it names the address.
Stage 6 — The money wall, end to end
This is the stage that makes this platform different, so here it is in full.
you ──▶ data call ──▶ 401 invalid_key nothing funds this key at all
└──▶ 402 payment_required nothing funds THIS product (next_action: top_up)
action_url on both: https://console.forum.bot/
│
├──▶ GET /account/balance ──▶ remaining_credits <= 0 (so money is the reason)
│
├──▶ POST /v1/account/topup ──▶ 201 { hosted_url: "https://checkout.stripe.com/..." }
│
├──▶ give BOTH urls to your owner ── a human pays on the provider's page
│ (card details never touch the Forum)
│
└──▶ poll /v1/account/usage ──▶ bought_credits > 0 (~15 s), then the SAME key
serves again within about seven minutes ──▶ resume
1. The wall. There are two of them, and both take the same path from step 2 on.
{ "type": "auth", "code": "invalid_key",
"message": "This key is not authorized right now. Check your account.",
"action_url": "https://console.forum.bot/",
"request_id": "req_a1b2c3" }
{ "type": "billing", "code": "payment_required", "next_action": "top_up",
"message": "this key is not funded for onecall3 — its free credits for this product are spent, or it has none, and the account has no purchased credits; top up at the address in action_url, or call a product this key is still funded for",
"action_url": "https://console.forum.bot/",
"request_id": "req_a1b2c3" }
action_url is the page to send a person to. It explains what happened in plain language, holds
no state, asks for nothing, and its address never changes.
The 401 does not say why, and that is deliberate: a data server knows only whether your key
is in the list it serves. GET https://data.forum.bot/account/balance, with the same data key, tells you whether
the reason is money — remaining_credits at or below zero and nothing left in product_credits.
The 402 needs no such question: it already names the product you cannot pay for. That route
answers for any key the platform issued, including one the data plane has stopped serving, which
is exactly the key that needs it.
2. Ask for the payment. You may call this at any time — before the wall as well as after — and a never-verified account may call it.
curl -s -X POST https://api.forum.bot/v1/account/topup \
-H "Authorization: Bearer $FORUM_SECRET" \
-H 'Content-Type: application/json' \
-d '{"amount_minor": 1000, "currency": "USD"}'
Your secret, no header. One page is open per account at a time: asking again while it is open returns the same page, and a new one opens only after it is paid or has expired.
{
"intent": {
"intent_id": "int_dd731574dbcb38dc8f2f612c",
"type": "topup", "state": "created",
"hosted_url": "https://checkout.stripe.com/c/pay/cs_live_…",
"amount_minor": 1000, "currency": "USD"
}
}
amount_minor is cents, and the minimum is 1000 — ten US dollars. USD only. Below the
minimum you get our own error, not the provider’s:
{ "type": "validation", "code": "invalid_params",
"message": "the minimum top-up is 1000 USD minor units", "next_action": "top_up" }
3. Hand it over. hosted_url is a one-time page at the payment provider and it expires;
action_url from the wall is the stable explainer. Give a human both. Do not open the payment
page yourself, do not scrape it, and never ask anyone for card details — the Forum has no field
anywhere that accepts a card, and anything claiming otherwise is not us. The payment page and the receipt are in The Bot Forum’s name: Extreme Electronics Ltd, trading as The Bot Forum, is the seller of every credit.
4. Wait, then resume. Credits appear about fifteen seconds after the payment completes. Poll
/v1/account/usage until balance.bought_credits rises. Do not open a second payment because
the first has not landed yet — that is how an owner gets charged twice. The platform will not
double-credit you, but it also cannot un-charge a card.
Then wait a little longer for the data plane. Your key comes back into a data server’s list when
the platform builds the next list and that server pulls it: under seven minutes at the platform’s
defaults, and under a second on a server that keeps the platform’s change feed open. It is the
same key — nothing to reissue, nothing to reconfigure. Keep retrying the data call until it
answers 200.
One credit is $0.001, so a $10 top-up is 10,000 credits — at 1 credit per One Call, 10,000 calls.
/v1/products is the authority on what each product costs.
Stage 7 — Verification
Verification means a real card is attached to the account. It uses the same machinery:
curl -s -X POST https://api.forum.bot/v1/account/verify \
-H "Authorization: Bearer $FORUM_SECRET"
{ "intent": { "intent_id": "int_...", "type": "verification", "state": "created",
"hosted_url": "https://checkout.stripe.com/c/pay/cs_live_…" } }
There is no amount_minor: verification places a zero-amount hold and charges nothing. Hand
the hosted_url to your owner exactly as in stage 6.
You do not have to verify first in order to buy credits. A successful payment of either kind
moves the account from untrusted to verified — the tier and the first bought credits arrive
on the same event.
Stage 8 — Keys: issue, list, rotate, revoke
All four take your secret. A data key cannot make, list, rotate or revoke keys — not even itself.
# issue a data key — the string is in this answer (key_id + key) and nowhere else, ever
curl -s -X POST https://api.forum.bot/v1/keys \
-H "Authorization: Bearer $FORUM_SECRET" -H 'Content-Type: application/json' \
-d '{"name": "my-agent"}' # the body is {} or {"name": "..."} — nothing else
# list — masked. The eight visible letters and the attributes only; no key string ever appears here
curl -s https://api.forum.bot/v1/keys -H "Authorization: Bearer $FORUM_SECRET"
# rotate — a new key with the same name; the old one turns terminal
curl -s -X POST https://api.forum.bot/v1/keys/key_646877662031cbcc/rotate \
-H "Authorization: Bearer $FORUM_SECRET"
# revoke — 204, and the data plane stops serving the key within about seven minutes
curl -s -X DELETE https://api.forum.bot/v1/keys/key_646877662031cbcc \
-H "Authorization: Bearer $FORUM_SECRET"
# replace the secret — a new one shown once; the old one is dead first; no data key is touched
curl -s -X POST https://api.forum.bot/v1/account/secret -H "Authorization: Bearer $FORUM_SECRET"
Every key of the account is an equal credential — it carries no scopes of its own, and it reaches
exactly the products the account can pay for right now, no more and no less — so a body naming
scopes (or budget_cap_credits) is refused with invalid_params, never silently ignored.
A revoked or rotated-away key answers key_revoked on the api host — the truth about itself. On
the data plane it answers invalid_key, like every other key that is not in the list a data
server serves from; the data plane never explains a key’s state. The 204 is immediate, and the
data plane follows it within the time the platform takes to build the next key list plus the time
each data server takes to pull it — under seven minutes at the defaults, under a second on a
server that keeps the change feed open. If you think a key leaked, revoke it and rotate.
Stage 9 — A whole agent, in one piece
Standard library only. It covers the walls in the order you will actually meet them.
import json, time, urllib.error, urllib.request, uuid
ENGINE, GATEWAY = "https://api.forum.bot", "https://data.forum.bot"
def call(method, url, bearer=None, body=None):
"""Returns (status, headers, parsed_body). Never raises on an HTTP error —
a wall is an answer, not an exception. The bearer is the secret on the api host
and the data key on the data host — the host names the string."""
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method)
if bearer:
req.add_header("Authorization", f"Bearer {bearer}")
if data:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=30) as r:
return r.status, dict(r.headers), json.loads(r.read() or b"{}")
except urllib.error.HTTPError as e:
return e.code, dict(e.headers), json.loads(e.read() or b"{}")
def sign_up(email):
_s, _h, out = call("POST", ENGINE + "/v1/accounts", body={"email": email})
# Both shown exactly once — persist them here, in two places: the secret with your
# account credentials, the data key where your data calls read it.
return out["secret"], out["data_key"]["key"]
def ask_the_owner_for_money(secret, dollars=10):
"""The one thing an agent cannot finish alone. Takes the SECRET: a data key cannot open
a payment. Asking again while a page is open returns the same page."""
_s, _h, out = call("POST", ENGINE + "/v1/account/topup", bearer=secret,
body={"amount_minor": dollars * 100, "currency": "USD"})
print("PAY HERE (one-time):", out["intent"]["hosted_url"])
print("What this is about: https://console.forum.bot/")
for _ in range(120): # ~15 s typical; be patient
time.sleep(5)
_s, _h, usage = call("GET", ENGINE + "/v1/account/usage", bearer=secret)
if usage["balance"]["bought_credits"] > 0:
return True
return False
TERMINAL = {"key_revoked", "key_blocked", "account_suspended", "not_found"}
def balance(key):
"""The balance is a call, never a header: no data response reports one. This route takes
the DATA KEY and answers even for a key the data plane has stopped serving. It returns
the PURCHASED credits; product_credits beside them says what each product has left today."""
_s, _h, out = call("GET", GATEWAY + "/account/balance", bearer=key)
return out.get("remaining_credits")
def fetch(secret, key, path):
for _attempt in range(8):
status, _headers, body = call("GET", GATEWAY + path, bearer=key) # the DATA KEY
if status == 200:
return body
code = body.get("code")
if code == "rate_limited":
time.sleep(int(body.get("retry_after", 5)))
elif code == "upstream_error":
time.sleep(2 ** _attempt) # not charged; safe to retry
elif code == "invalid_key":
# Nothing funds this key at all — or it is unknown, revoked, expired, blocked or
# suspended, and a data server cannot tell those apart. Ask the balance: at or
# below zero, money is the reason, a human clears it, and the SAME key serves
# again once the data servers pull the next list.
left = balance(key)
if left is None or left > 0:
raise RuntimeError(f"not authorized: check the account at "
f"{body.get('action_url', '')} ({body['request_id']})")
if not ask_the_owner_for_money(secret):
raise RuntimeError(f"nobody paid; {body['action_url']} — {body['request_id']}")
time.sleep(60) # under seven minutes at the defaults
elif code == "payment_required":
# The key is fine; THIS product is not funded — its free credits are spent, or it
# gives none, and nothing was bought. Same recovery, and a product this key can
# still pay for keeps working meanwhile.
if not ask_the_owner_for_money(secret):
raise RuntimeError(f"nobody paid; {body['action_url']} — {body['request_id']}")
time.sleep(60) # under seven minutes at the defaults
elif code == "verification_required":
raise RuntimeError(f"a human is needed: {code} -> {body.get('action_url', '')}")
elif code in TERMINAL:
raise RuntimeError(f"terminal: {code} ({body['request_id']})") # never retry
else:
raise RuntimeError(f"unhandled: {code} ({body.get('request_id')})")
raise RuntimeError("gave up after 8 attempts")
if __name__ == "__main__":
secret, key = sign_up(f"agent-{uuid.uuid4().hex[:8]}@example.com")
print(fetch(secret, key, "/onecall/current?lat=51.5074&lon=-0.1278")["data"][0]["temp"])
Over MCP — the same loop as six tools
The full description — what the server is, the six tools, the key rule, the per-client entries — is its own page, https://forum.bot/mcp (as markdown: https://forum.bot/mcp.md, the same text the server serves at https://mcp.forum.bot/). The short form:
If your agent runs in a harness that speaks the Model Context Protocol — Claude Code, Cursor,
VS Code, Windsurf, Gemini CLI, Codex, the OpenAI Responses API, LangChain, AWS AgentCore — it
can do everything above through six tools on https://mcp.forum.bot/mcp instead of raw HTTP:
list_products, get_product and sign_up need no string; fetch_data uses your data key,
and account_status and get_topup_link take your secret. Every tool call is one of the public calls above; every wall
comes back as the same envelope, with the HTTP status beside it; no tool pays. The server card
is at https://mcp.forum.bot/mcp/server-card.
The generic entry — the endpoint and one header — is all any client needs:
{ "url": "https://mcp.forum.bot/mcp", "headers": { "Authorization": "Bearer <your data key>" } }
The header a client stores is the data key — it serves fetch_data. The secret stays
with the owner, never in the server entry, and rides account_status and get_topup_link as
the secret argument. In the session an agent signs up in, the data key it just received
can ride fetch_data as the data_key argument until its owner stores the header: most clients
read a new header only after the owner reconnects or restarts them. The answer of sign_up
carries the entry to store (configure) beside the two once-shown strings.
Per client, verbatim:
- Claude Code —
claude mcp add --transport http forum https://mcp.forum.bot/mcp --header "Authorization: Bearer <data key>", or in.mcp.json:{"mcpServers": {"forum": {"type": "http", "url": "https://mcp.forum.bot/mcp", "headers": {"Authorization": "Bearer ${FORUM_KEY}"}}}}; after an edit,/mcp→ Reconnect. - Cursor (
~/.cursor/mcp.json) and Windsurf (~/.codeium/windsurf/mcp_config.json, withserverUrlin place ofurl):{"mcpServers": {"forum": {"url": "https://mcp.forum.bot/mcp", "headers": {"Authorization": "Bearer <data key>"}}}}. - VS Code (
.vscode/mcp.json):{"inputs": [{"type": "promptString", "id": "forum-key", "description": "The Bot Forum data key", "password": true}], "servers": {"forum": {"type": "http", "url": "https://mcp.forum.bot/mcp", "headers": {"Authorization": "Bearer ${input:forum-key}"}}}}. - Gemini CLI (
~/.gemini/settings.json):{"mcpServers": {"forum": {"httpUrl": "https://mcp.forum.bot/mcp", "headers": {"Authorization": "Bearer <data key>"}}}};/mcp reloadafter an edit. - Codex CLI (
~/.codex/config.toml):[mcp_servers.forum], thenurl = "https://mcp.forum.bot/mcp"andbearer_token_env_var = "FORUM_KEY". - OpenAI Responses API — a tool
{"type": "mcp", "server_label": "forum", "server_url": "https://mcp.forum.bot/mcp", "headers": {"Authorization": "Bearer <data key>"}, "require_approval": "never"}. - Claude Desktop (its JSON file is stdio-only) through the
mcp-remotebridge:{"mcpServers": {"forum": {"command": "npx", "args": ["mcp-remote", "https://mcp.forum.bot/mcp", "--transport", "http-only", "--header", "Authorization:${AUTH_HEADER}"], "env": {"AUTH_HEADER": "Bearer <data key>"}}}}. - claude.ai and ChatGPT connectors take the URL with no key: the three keyless tools work;
fetch_datauses thedata_keyargument and the two account tools thesecretargument in the session that signed up, until a key can be configured there.
Stage 10 — The rules you must not break
- Never put a credential in a URL. The secret and the data keys are opaque bearer tokens in the
Authorizationheader. A key in a query string ends up in logs, referrers and caches. - Never retry a wall in place.
key_revoked,key_blockedandaccount_suspendedmean stop.invalid_keyon the data plane means stop calling and send a human toaction_url; the same call keeps failing until a person acts.payment_requiredmeans the same for that one product — a person tops up, though a product this key can still pay for keeps working meanwhile. Spinning on any of them is how an account gets flagged for abuse. - Never blind-retry a
400. Fix the call. - Never invent a credential or a marker. The platform mints the secret and every data key; no route reads a string you made up. A dropped signup reply is recovered by your owner at the console; a repeated payment ask returns the page already open.
- Never ask a person for card details, and never accept them yourself. Payment happens on the provider’s own page. There is no Forum surface anywhere that takes a card.
- Attribute the data when
meta.licence.attribution_requiredis true. The text to use is in the same response. - Keep the
request_idof anything that went wrong. It is how the exact call is found.
Terms of service: https://forum.bot/tos/last.json · Prices: https://api.forum.bot/v1/products · Machine quickstart: https://forum.bot/llms.txt · Full API: https://api.forum.bot/openapi.yaml · Something wrong, no account needed: mailto:info@forum.bot