Migrating from Chatbase to Ringside
Chatbase ships a finished chatbot. Ringside ships the pieces, retrieval included. When your no-code bot runs into the edge of the dashboard (provider choice, prompt control in your repo, your own UI, per-end-customer billing), Ringside is the next step. Managed RAG means you don't give up your training documents to get there.
Status: v1 (2026-04-20).
See also: Migration library index.
Should you move
Someone edited the system prompt in the Chatbase dashboard on Thursday afternoon. By Friday the bot was quoting a 14-day refund window instead of 30, and support was fielding the fallout. There's no diff to read, no commit to revert and nobody who reviewed the change, because the prompt lives in a textarea in a web app rather than in your repository next to everything else you ship. That's the week most teams start looking for an API.
The dashboard is the whole product, which is exactly why it's good for the first six months and awkward after that. A prompt you can't code-review is one symptom. The others show up on the invoice.
Chatbase bills you for total message volume across every one of your customers. One number. If the agency that resells your bot to 40 of their own clients wants a per-client breakdown, you cannot produce one, because Chatbase does not model your customers at all. Ringside does, and that single difference is what most of this guide is about.
Move when any of these is true.
- One bill covering all your customers, with no way to split it. Ringside gives every end-customer a Customer object. Pass
user: "cus_42"on a call andGET /v1/customers/cus_42/usagetells you what that account cost you, withGET /v1/customers/cus_42/margin?group_by=daysitting next to it for what you made on them. - No hard cap when something runs away overnight.
PATCH /v1/customers/cus_42 {"monthly_budget_usd": 25}stops the next call that would cross $25 with402 customer_budget_exceeded, before it reaches a provider. Per customer, not per account. - No answer when a customer disputes usage. Every response carries
X-Request-Idand every charge is an attributed event with a model, token counts and a timestamp. - A prompt and a document set you cannot get under version control. Assistant instructions go in your repo and ship through your normal pipeline. Vector stores are created and populated by API calls you can put in a migration script.
- You're locked to one provider's models. 19 providers behind one SDK, swapped per request with a
fc:<provider>/<model>ref or repointed across every call site at once withslot:<alias>.
If none of those bite yet, stay on Chatbase. It does the job it's built for and this guide will still be here.
The part people expect to lose, and don't
The old version of this guide told you to go and buy a vector database. That advice is dead. Ringside ships managed RAG, so the training documents that were the reason to stay on Chatbase come across as a first-class object.
You create a store with POST /v1/vector_stores, upload files to /v1/files with purpose="attachments", attach them, and the platform chunks, embeds and indexes them for you. Then you hand the store to an Assistant with the file_search tool and it retrieves at answer time, with file_citation annotations pointing back at the source file. Ingestion takes 25+ MIME types, PDFs included, so a folder of product manuals goes in the way you'd hope.
Two things sit on top of that, and they're the reasons to prefer it over rolling your own index. graphrag_enabled: true on a store builds a fact graph over your documents, so a question whose answer is split across three files gets joined instead of shrugged at. And a store created with encryption: "managed" seals chunk text and vectors at rest under a per-store key, which matters if the documents are contracts rather than FAQs.
One caveat on sealing, and it's a real one. Sealed stores accept text only today: text, Markdown, plain text, JSON, XML and YAML, up to 25 MB per file. Push a PDF or a DOCX at a sealed store and ingest fails. If your documents are PDFs and you want them sealed, convert to text or Markdown first. Plaintext stores take the full MIME range.
If you already run your own index, keep it. /v1/embeddings is there and the bring-your-own path is documented below. Managed RAG is the default because it's less code, not because the other option went away.
How risky is this
You can run both at once. Chatbase keeps serving your live widget while you stand up the Ringside path behind a flag and send it a fraction of traffic, which is the honest way to find out whether your retrieval quality holds up on real questions rather than on the six you thought to test.
Nothing about the move is one-way. Your training documents stay in whatever folder you exported them to. Your prompt is a text file. Ringside's chat endpoint is OpenAI-shaped, so if you decide six weeks in that you want a different platform underneath, you point base_url somewhere else and keep the same SDK call.
The genuinely new work is the UI, and that's the part to schedule properly. Budget a couple of days.
What you keep. What you lose. What you gain.
You keep
- Your training documents. Export from Chatbase, upload to a Ringside vector store, attach, done. No third-party vector database in the path.
- Your conversation history.
/v1/customers/:id/conversationsreplaces Chatbase's thread storage 1:1. - Your chatbot persona / system prompt. Straight into a Ringside Assistant via
POST /v1/assistants {instructions: "..."}, and this time it lives in git.
You lose
- The widget. You build or buy a chat UI. shadcn/ui, Vercel's
aipackage or any of the OSS React chat components get you there in a day. - Chatbase's analytics dashboard. Replaced by
/ringside/app/customers/[id]/usageplus the request log at/ringside/app/logs. - Built-in lead capture form. Re-implement in your UI, post to your own backend, then call
POST /v1/customers.
You gain
- 19 LLM providers, one SDK. Swap Claude, Llama 3, Gemini or Mistral on the same client code.
- Per-end-customer billing and hard budget caps.
- Managed RAG with
file_search, GraphRAG and sealed stores. - Webhooks on 34 registered event types, HMAC-signed and retried.
- Client Tokens. Short-lived JWTs your browser uses directly after your backend mints them, scoped to a single Customer, with an optional Origin allowlist.
- Margin reporting, per Customer, per model, per day.
- Assistants API wire-compat, if you want OpenAI-SDK Assistants behaviour.
- GDPR hard-delete.
DELETE /v1/customers/:id?hard=true.
Step-by-step migration
Step 1: Sign up and create a server token (5 minutes)
ringside.fightclub.pro/register?intent=ringside gets you $10 credit, no card. Create a server token at /ringside/app/api-keys/new. It's ko_ followed by 64 hex characters, shown once.
Step 2: Export your Chatbase chatbot config
From the Chatbase dashboard, export the chatbot's instructions and its training documents. We'll call them instructions.txt and docs/*.md. Commit both.
Step 3: Create a vector store and load your documents
pythonfrom openai import OpenAI client = OpenAI(api_key="ko_...", base_url="https://api.fightclub.pro/v1") store = client.vector_stores.create( name="support-docs", metadata={"source": "chatbase_migration"}, ) for path in training_docs: f = client.files.create(file=open(path, "rb"), purpose="attachments") client.vector_stores.files.create(vector_store_id=store.id, file_id=f.id)
Attach returns pending. Ingest runs asynchronously, so poll each file until it reaches completed or failed, and read last_error on a failure. Only failed and cancelled files are retryable. If you'd rather not poll, subscribe to the vector_store.file.completed webhook.
Turning on the fact graph is one field, at create time or later via PATCH:
pythonstore = client.vector_stores.create(name="support-docs", graphrag_enabled=True)
For a store that has to hold sensitive text, add encryption: "managed" at create. Remember the text-only limit above before you point it at a folder of PDFs.
Step 4: Map each Chatbase "chatbot" to a Ringside Assistant
pythonassistant = client.beta.assistants.create( name="Support Bot", instructions=open("instructions.txt").read(), model="fc:openai/gpt-4o-mini", # or fc:anthropic/claude-haiku-4-5, etc. tools=[{"type": "file_search"}], tool_resources={"file_search": {"vector_store_ids": [store.id]}}, ) print(assistant.id) # asst_...
Retrieval now happens inside the run. Answers come back with file_citation annotations, so your UI can show which document a claim came from, which is something the Chatbase widget never gave you.
Already running your own index? Skip the vector store. POST /v1/embeddings gives you the vectors, you keep pgvector or whatever you have, and you expose retrieval to the model as an ordinary function tool:
pythonemb = client.embeddings.create( model="fc:openai/text-embedding-3-small", input=[doc.read() for doc in training_docs], ) # Store emb.data[i].embedding alongside your chunks, then declare a # search_docs function tool that does top-k lookup and returns passages.
Step 5: Map each Chatbase end-user to a Ringside Customer
Ringside auto-creates Customers on first user=<id> pass. Pre-populate if you want budgets and metadata set from the start:
pythonfor cbase_user in chatbase_users_export: client.post( # POST /v1/customers (raw HTTP here; no SDK method yet) "/customers", json={ "external_id": cbase_user["id"], "name": cbase_user["name"], "monthly_budget_usd": 10, # hard cap per Customer "rpm_limit": 60, "metadata": {"source": "chatbase_migration"}, }, )
Step 6: Build your own chat UI
Minimal React using Client Tokens for browser-safe calls plus SSE streaming:
jsx// Backend mints a Client Token for this Customer (keep the server token server-side) // POST /v1/customers/cus_42/client_tokens → { token: "eyJ...", expires_at: ... } // Frontend (React), using the Client Token directly: import { useState } from "react"; export function Chat({ clientToken, conversationId }) { const [messages, setMessages] = useState([]); async function send(text) { const resp = await fetch( `https://api.fightclub.pro/v1/customers/cus_42/conversations/${conversationId}/messages`, { method: "POST", headers: { Authorization: `Client ${clientToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ role: "user", content: text, stream: true, }), } ); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let assistant = ""; while (true) { const { value, done } = await reader.read(); if (done) break; // Parse OpenAI-shape SSE chunks const chunk = decoder.decode(value); for (const line of chunk.split("\n")) { if (!line.startsWith("data: ")) continue; const payload = line.slice(6).trim(); if (payload === "[DONE]") return; const delta = JSON.parse(payload).choices?.[0]?.delta?.content ?? ""; assistant += delta; setMessages((m) => [...m.slice(0, -1), { role: "assistant", content: assistant }]); } } } return ( <div> {messages.map((m, i) => <p key={i}>{m.role}: {m.content}</p>)} <input onKeyDown={(e) => e.key === "Enter" && send(e.currentTarget.value)} /> </div> ); }
Your backend mints a new Client Token every 15 minutes (the default TTL) and serves it to the authenticated user. That's about 20 lines of server code.
Step 7: Migrate conversations (optional)
To preserve chat history, export Chatbase conversations, create Ringside Conversations, replay the messages:
pythonfor cbase_conv in chatbase_conversations_export: conv = client.post( f"/customers/{cbase_conv['user_id']}/conversations", json={"metadata": {"source_chatbase_id": cbase_conv["id"]}}, ) for msg in cbase_conv["messages"]: client.post( f"/customers/{cbase_conv['user_id']}/conversations/{conv['id']}/messages", json={"role": msg["role"], "content": msg["content"], "skip_llm": True}, )
(skip_llm: true appends a message without triggering a new LLM run, which is what you want for replay. Not shipped yet, available on v1.1+.)
Feature mapping
| Chatbase | Ringside equivalent |
|---|---|
| "Chatbot" (template + training docs + model) | POST /v1/assistants with the file_search tool + a vector store |
| Training documents | POST /v1/vector_stores, POST /v1/files with purpose="attachments", then attach |
| Bring-your-own index (alternative) | POST /v1/embeddings + your own store, exposed as a function tool |
| Multi-hop questions across documents | graphrag_enabled: true on the store |
| Sensitive documents | encryption: "managed" on the store (text/Markdown/JSON/XML/YAML only, 25 MB cap) |
| End-users ("chat users") | POST /v1/customers (or auto-create via user: "<id>") |
| Conversations (thread history) | POST /v1/customers/:id/conversations + /messages |
| Chat widget | Build your own (shadcn/ui, custom React, whatever) |
| Lead capture forms | Your own form, then POST /v1/customers on submit |
| Usage dashboard | /ringside/app/customers/[id] + /ringside/app/usage |
| Webhook on new message | POST /v1/webhooks {events: ["run.completed", ...]}, 34 registered event types |
| Per-chatbot model selection | model: "fc:<provider>/<model>" or slot:<alias> on each call |
| Message rate limits | Per-Customer rpm_limit / tpm_limit via PATCH /v1/customers/:id |
| Bulk offline processing | POST /v1/batches (JSONL in, 24h completion window, batch-rate billing) |
| n/a | Per-Customer monthly budget caps (monthly_budget_usd) |
| n/a | Margin reporting (/v1/customers/:id/margin) |
| n/a | 19 provider choices |
| n/a | Client Tokens (browser-safe Ed25519 JWTs) |
| n/a | GDPR hard-delete |
Gotchas
- UI is all you. Chatbase's theming panel, widget embed and analytics overlay have no equivalent. Budget 1-3 days to build or buy a chat UI.
- Your backend must mint Client Tokens. Never ship a
ko_server token to the browser. Your server mints short-lived Client Tokens viaPOST /v1/customers/:id/client_tokens, the browser uses those to callchat/completionsandconversations/*/messagesdirectly, and you refresh before expiry. - File upload purpose matters.
purpose="assistants"returns 400. The allowed values areattachments,batchandvision. Vector store files wantattachments. - Ingest is asynchronous. Attach returns
pendingand moves throughin_progresstocompleted,failedorcancelled. A file sitting atpendingis not an error yet. Readlast_erroronfailed. - Sealed stores are text-only today. Text, Markdown, plain text, JSON, XML, YAML, 25 MB per file. A sealed PDF fails ingest. Plaintext stores take PDFs and 25+ MIME types.
- Lazy Customer auto-creation is on by default. Passing
user: "cus_xyz"on a first call creates the Customer with no budget and no metadata. Pre-create viaPOST /v1/customersif the budget matters. - Customer ids are
cus_plus hex. Notcust_. - Conversation lock. Only one LLM call runs against a conversation at a time. If your UI fires a second message while the first is still streaming you get
409 conversation_locked. Debounce, or open a second conversation. - 5,000-message cap per conversation. After 5k messages
POST /messagesreturns409 conversation_full. Long-running FAQ bots should rotate conversations, one per session or one per calendar week.
FAQ
Q: What if I want Chatbase-like simplicity with more flexibility? A: Ringside's Assistants API (/v1/assistants, /v1/threads, /v1/threads/:id/runs) is the closest 1:1 to Chatbase's "chatbot" concept, a named agent with instructions that keeps thread state, and with file_search attached it also keeps the training documents. See openai-assistants-to-ringside.md for the full OpenAI-SDK pattern.
Q: Does Ringside do content moderation like Chatbase? A: Yes. POST /v1/moderations (OpenAI-compat, free at-cost on Pro+). Call it on inbound user messages before the LLM. The moderation.flagged webhook fires on flagged content.
Q: Do I need a separate billing system to charge my end-customers? A: Stripe plus Ringside is the standard combo. Ringside tells you what each Customer cost you and Stripe charges them. Sync GET /v1/customers/:id/usage nightly into Stripe meters or your own invoicing.
Q: How much does this cost vs. Chatbase? A: Chatbase's Pro plan is $40/mo for 2,000 messages on GPT-4o-mini, about $0.02 a message. Ringside is $99/mo base on Pro plus roughly $0.0003 a message on gpt-4o-mini plus 6% markup, about $0.00032 a message, with vector storage billed separately per GB-day. Above 10k messages a month Ringside is cheaper. Under 2k, Chatbase is simpler and cheaper.
Q: Support? A: Open a ticket and a human picks it up. For hands-on help moving a live bot over, book a migration call.
Next steps
- Managed RAG, in full:
ringside.fightclub.pro/rag. - Playground:
ringside.fightclub.pro/try. - Support: open a ticket.
Related migrations
- OpenAI Chat Completions → Ringside
- OpenAI Assistants → Ringside
- Anthropic Messages → Ringside
- OpenRouter → Ringside
- Index
Corrections or feedback: open an issue.