Start with AI
How to hand the work of adding chat to an AI such as Claude, ChatGPT or Cursor. You do two things: sign up and get one account key, then copy a prompt below and paste it to the AI. The AI creates the app, gets the keys and writes the code.
The AI reads
https://chat.gravex.app/en/llms.txtto learn how this service works. Put that address in your prompt.
Step 1. Sign up and get one account key
You do this part yourself. An AI cannot sign up or issue your first key for you. Leave the rest to the AI.
- Sign up in the console. Enter the 6-digit code sent to your email (there is no password).
- In the top menu choose API keys → New key → give it a name (for example
laptop .env). The key (ck_…) is shown once, so copy it right away. - Put it into your project's
.envyourself. Do not paste this key into the AI chat. Conversations are kept as records. Tell the AI "readCHAT_ACCOUNT_KEYfrom.env"; the first prompt below already does.
# .env — you fill in the value
CHAT_ACCOUNT_KEY=ck_...A ck_ key has full power over your account. Keep it only on a server or in a local .env, never in frontend code or a git repository.
Revoking it on the API keys screen when you are done is recommended. Apps you already created and their pk_ and sk_ keep working after you revoke it —
the account key is only the credential for calling the Console API. After revoking it, also delete the CHAT_ACCOUNT_KEY line from .env.
How to use it is in the Console API guide.
When the AI creates the app, the response carries pk_… (the public value for your frontend) and sk_… (a secret for your server only) together.
The sk_ is shown only in that response, so make the AI write it straight into VITE_CHAT_PK and CHAT_SECRET_KEY in .env and never print it to a screen or the chat.
If you would rather create the app yourself, press New app in the console and put the two keys into .env by hand; then drop the first two lines of the prompt below.
# .env — the AI fills these in when it creates the app
VITE_CHAT_PK=pk_...
CHAT_SECRET_KEY=sk_...Every document uses these same variable names. The frontend reads only VITE_CHAT_PK — Vite puts only variables that start with
VITE_ into the browser bundle. CHAT_SECRET_KEY, CHAT_ACCOUNT_KEY, CHAT_WEBHOOK_SECRET and CHAT_GUEST_SECRET are read only on the server.
Never widen Vite's envPrefix to CHAT_ — that ships sk_ and ck_ into the browser bundle as they are. With a frontend other than Vite,
put that framework's public prefix on the pk_ only (for Next.js, NEXT_PUBLIC_CHAT_PK).
Step 2. Copy a prompt
Each card has a one-line what you get and a prompt to copy. Go in order or pick what you need. In a new
conversation, put the first line of prompt 1 (Read …llms.txt) in front.
1. A first chat room (lobby)
What you get: one chat room where people talk in real time — under their member names if the project has login, as guests if it doesn't.
Read https://chat.gravex.app/en/llms.txt, create an app with the CHAT_ACCOUNT_KEY in .env, fill pk_/sk_ into .env, then add a lobby chat to my project.
- The allowed origin is my frontend dev server's address (Vite's default is http://localhost:5173). If the port is different, or it is taken and the server starts on another port, use the address it actually runs on. If the address changes later, fix it with PATCH /api/apps/{id}/keys/{keyId} — it replaces the whole origin list, so send the existing values too.
- Create the app with POST /api/apps, exactly as the Console API guide (https://chat.gravex.app/en/docs/console-api) says. Write publishableKey.value to VITE_CHAT_PK and secretKey.value to CHAT_SECRET_KEY in the file right away. The sk_ is shown only in that response, so don't print it to the screen, a log or the chat.
- Read CHAT_ACCOUNT_KEY only in the script that creates the app. Never put it in app code or in the frontend bundle. Expose only VITE_CHAT_PK to the frontend and don't widen Vite's envPrefix. Also check that .env is in .gitignore.
- Look at my project's framework and login method first and add a simple chat (one lobby room) that fits them. If I have no project, build the simplest React (Vite) + Express one.
- Backend: an endpoint that issues chat tokens, and creating the room 'lobby' (get-or-create). If there is login, issue tokens to signed-in users with chat.token(); if there is none, issue guest identities with chat.guest() as the 'Guest users' guide says. Don't invent a demo login. Create the CHAT_GUEST_SECRET that guests need in .env as a random value of at least 32 characters.
- Frontend: connect with the @kispi/chat SDK, show the message list and an input box, and send messages
- When you're done, tell me the result of checking each item of the 'UI checklist' in llms.txt, how to check it locally, and how to revoke the CHAT_ACCOUNT_KEY now that you're finished with it (the app and its pk_/sk_ keep working after that).2. Members and visitors
What you get: signed-in members talk under their own names and visitors without an account talk as guests, in the same room.
In the chat above, let both signed-in members and visitors who are not signed in talk.
Follow the 'Guest users' approach in the docs: issue guest identities with chat.guest(), and never sign an id the browser sends.
Show guest names as 'Guest-' plus a short number, and if CHAT_GUEST_SECRET is not in .env yet, create it as a random value of at least 32 characters.3. A comment room per post
What you get: every post (product, video, article) gets its own comment chat room.
Add a dedicated chat room (comment room) to each post's detail page.
Use the room key 'post-<post id>'. The backend creates the room with get-or-create and the frontend opens it with roomByKey.
Opening a post should show the latest messages, and scrolling up should load older ones (loadOlder).4. Thread replies
What you get: a "Reply" on a message opens its thread in a side panel, and the original message shows a reply count.
Add a 'Reply' button to every message and build Slack-style threads.
- Send a thread reply with send({ text, threadId: original message id }), and show only messages without a threadId on the main timeline.
- Show the reply count (thread.count) on the original message, and open the thread in a right-hand panel when it is clicked.
- Quoting a specific message is replyTo; build it separately from threads.5. Emoji reactions
What you get: put a 👍 or any emoji on a message and watch the count change live.
Add emoji reactions to messages. Hovering a message shows an emoji picker button, and picking one calls room.react / room.unreact.
Show counts per emoji under the message and highlight the ones I pressed. Use the server's count as it comes, and handle the limit of 20 distinct emojis per message.6. Mentions and unread badges
What you get: call people with @name, and see unread and mention badges in the room list.
Add mentions and unread indicators.
- Typing @ in the input shows an autocomplete of the room's participants. Picking one sends entities with { type: 'mention', userId, offset, length }. Highlight mentions in the message body.
- In the room list (chat.rooms.list), draw unread and mention count badges per room, and call markRead when the room is viewed.
- Get the total unread count for the app icon from chat.unread().7. Typing and online indicators
What you get: "Hannah is typing…" and who is in the room right now.
Add typing and online indicators.
- Call room.typing() as I type (the SDK throttles it), and show 'X is typing…' from the typing event. Skip my own events and clear it when more than 5 seconds pass after the last event.
- Show the number of people currently in the room (room.presence) in the room header, and show the list when it is clicked.8. 1:1 DM
What you get: "Send message" on a profile opens a conversation between two people only.
Add a 'Send message' button to user profiles and build 1:1 DMs.
The backend calls rooms.ensureDM with the two user ids and returns the room id, and the frontend opens that room.
Build the DM list screen from chat.rooms.list() and show the last message and unread count.9. Announcement channel and system messages
What you get: a room only operators write in and everyone reads, plus notices sent by your server.
Create an 'Announcements' room that only operators can write in while everyone else just reads. Use type: 'channel' and set my operator account as owner or admin.
Also write a backend function that sends system messages such as 'Maintenance starts in 10 minutes' to that room and the lobby (messages.send, kind: 'system').10. Word filter and image uploads
What you get: bad words masked, and only photos that pass a check get posted.
Read https://chat.gravex.app/en/docs/uploads and add image uploads to the chat. The engine does not store files, so they go into my own S3 bucket.
- Follow the document's order: presigned POST, keep files in a private prefix before the check, and move only what passes the Rekognition check to the public prefix
- In the before_publish webhook, confirm that an attachment URL is this sender's file under my public prefix, and mask bad words with * in the same hook.
- Don't put AWS credentials in code; use an IAM role, and give me a separate list of what I have to do in the AWS console.11. Notifications through a webhook
What you get: your server notices a new message and sends a push or an email.
Create a chat webhook endpoint. Follow the 'Webhooks' document: verify the signature over the raw body, process each delivery only once, and answer 200 first, then work asynchronously.
When message.created arrives, notify the room's members other than the sender. I haven't chosen the channel (web push, FCM, email…), so ask me first, and leave a TODO where the notification goes until I decide.12. Handling reports and exporting data
What you get: tools to block a problem user and remove their messages, and a backup of all chat data.
Write operations scripts.
- A command that takes a user id, bans them (users.ban) and deletes all their messages (users.purgeMessages, repeated until purgeCapped is false)
- A command that downloads the whole app's data. Call GET /v1/export with the sk_, save it as an NDJSON file, and tell me the file is incomplete if the last line is not end.13. A client in another language (iOS, Android, Flutter, Python)
What you get: a chat client written in Swift, Kotlin, Dart, Python or another language without the official SDK: connecting, subscribing to rooms, sending and receiving messages, reconnecting and filling gaps.
Read https://chat.gravex.app/en/llms.txt and, using only the 'REST API reference' and 'Real-time protocol (WebSocket)' in the docs, build a chat client in Swift. Kotlin or Flutter (Dart) is fine instead.
- First read the checklist 'Building a client without the SDK' at the end of 'Real-time protocol' and implement its items one by one.
- Connecting (auth then hello) and ping, subscribing to a room (since), sending messages (dedup with clientMessageId), applying events
- Reconnect with exponential backoff plus jitter, and after reconnecting, fill the seq gap of each room from REST history.
- Make the user token a function that fetches it from my backend, and fetch a new one on every connect. Never put the sk_ in the app.
- When you're done, give me a table of which checklist items you did and didn't do.UI checklist
When the AI has finished the screens, have it check them against this list (prompt 1 already asks for it). These are the things the SDK does not do for you and that are easy to miss.
- Redraw on
reset. Throw away the rendered list and draw again fromroom.messages. Appending to the old list leaves a gap. - Keep the reading position when loading the past. Correct the scroll position by what
loadOlder()added on top, and follow new messages down only when the user is already at the bottom. - Leave thread replies out of the main timeline.
room.messagesalso holds rows that have athreadId. - Skip your own typing events and clear the indicator after about 5 seconds.
typingalso reaches the sender, and there is no "stopped typing" event. - Fix reactions yourself on rows read with
history(). The SDK only updates rows insideroom.messages. For a list drawn fromhistory(), such as a thread panel, usecountanduserIdfromreaction.added/reaction.removedto fix the count andmyReactions. - Draw reaction counts from the server.
react()changesmyReactionsfirst and fixes the count when the event arrives; until then, if there is no count, draw just the emoji. Treat a row withoutmyReactionsas one you have not reacted to. - Insert the body as plain text. Never insert
textas HTML (innerHTML,dangerouslySetInnerHTML,{@html}). Highlight mentions by slicing the text and wrapping the pieces. - Wait on
rate_limited. Block sending for theChatError'sretryAfterMsand show the time left.
Ask first whether it is possible
You can ask before you build anything. The AI reads llms.txt and answers within what this service supports.
Read https://chat.gravex.app/en/llms.txt and answer. Don't implement anything; just tell me whether it is possible and how. If the docs don't say, say you don't know.
1. Can members and visitors both chat?
2. Are comments or threads possible?
3. Are emoji reactions possible?
4. What about read marks or unread message counts?
5. Do you send app push notifications?
6. Can people upload photos? Where are they stored?
7. What about 1:1 chat and announcement-only rooms?
8. Can I take all my data out later?
9. Can I connect an iOS or Android app too? What if there is no official SDK?What is not supported
Listed up front so an AI does not make up a yes. Where there is a way around it, it is next to the item.
| Not supported | Instead |
|---|---|
| Sending push notifications | Receive the message.created webhook and send through FCM, web push or email from your server (prompt 11) |
| Storing files | The engine carries only the URL and metadata. Upload to your own bucket (Image uploads and moderation) |
| Official native SDKs (iOS, Android, Flutter) | The official SDK is JS/TS only, but the REST API (REST API reference) and the real-time protocol (Real-time protocol) are public, so an AI can build a client in another language (prompt 13) |
| Login and sign-up | The engine does not know who anyone is; it trusts the token your backend signs. Visitors come in as guests (prompt 2) |
| Automatic word and image checks | We open a hook (before_publish) and you make the call (prompt 10) |
| Message search with a user token | Search (q) works with the server key (sk_) only. Put a search API on your backend |
| Voice and video calls | None. Only text chat and attachment URLs |
When you get stuck
If the AI's code does not run, ask with the error message: "Read https://chat.gravex.app/en/llms-full.txt and find
the cause of this error." llms-full.txt holds all the docs and the SDK reference in one file. If your AI cannot open
addresses, paste the contents of that file into the conversation and start from there. To read the docs yourself,
start with Getting started.