# Warid developer guide > How to add "Connect with Warid" (OAuth 2.0 with PKCE) to a SaaS app, call the API with the user's token, send WhatsApp, Instagram and Messenger messages (including WhatsApp templates and verification codes), open their dashboard with a sign-in link, and receive webhooks. - Human-readable docs: https://warid.chat/docs - OpenAPI document (every endpoint, request and response): https://api.warid.chat/docs/json - Create the app (client ID and secret): https://warid.chat/developers --- # Connect with Warid Let your users connect their Warid to your app with one button. Warid puts a business's WhatsApp, Instagram and Messenger conversations in one inbox, with an AI agent that answers customers. "Connect with Warid" works like "Sign in with Google": your user clicks a button, approves, and your app gets a token for their workspace. With it you can read and answer their conversations, and open their Warid dashboard without asking them to log in again. > **Building with an AI assistant?** Paste this link into ChatGPT, Claude or Cursor and ask it to add Connect with Warid to your app: https://warid.chat/llms.txt ## 1. Create your app In Warid, open **Developers** and click **Create app**. Pick a logo (or upload yours), then enter a name and your callback URL, the page people return to after they approve. You get a **Client ID** (`mci_…`) and a **Client secret** (`mcs_…`). The secret is shown once; keep it on your server. ![Create your app in Developers](https://warid.chat/guide/create-app.jpg) ![Your client ID and secret, ready to copy](https://warid.chat/guide/app-ready.jpg) ## 2. Add the button When the user clicks your "Connect with Warid" button, make a random `state` and a PKCE `code_verifier`, save both in their session, and send them to: ```http https://warid.chat/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/warid/callback &state=RANDOM_STATE &code_challenge=BASE64URL(SHA256(code_verifier)) &code_challenge_method=S256 ``` They sign in to Warid (or sign up), pick their workspace and approve. Leave out `scope` to ask for everything your app is enabled for, or pass a space-separated list from the table below. ![The consent screen your users see](https://warid.chat/guide/consent.jpg) ## 3. Handle the callback We send the browser back to your `redirect_uri` with `?code=…&state=…` (or `?error=access_denied`). Check that `state` matches, then swap the code for tokens from your server within 60 seconds: ```bash curl -X POST https://api.warid.chat/oauth/token \ -d grant_type=authorization_code \ -d code=THE_CODE \ -d redirect_uri=https://yourapp.com/warid/callback \ -d code_verifier=THE_VERIFIER \ -d client_id=YOUR_CLIENT_ID \ -d client_secret=YOUR_CLIENT_SECRET ``` ```json { "access_token": "mat_...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "mrt_...", "scope": "conversations:read conversations:write ...", "workspace_id": "b5d3d605-c177-418a-8e46-acd8c6e9c6f8" } ``` Save both tokens with your user. The access token lasts an hour. Get a new pair with `grant_type=refresh_token&refresh_token=mrt_…` (plus your client ID and secret); each refresh token works once. ## 4. Call the API Send the access token as a Bearer token. Everything is under `https://api.warid.chat/api/v1`. ```bash # Their conversations, newest first curl https://api.warid.chat/api/v1/conversations -H "Authorization: Bearer mat_..." # Reply in one curl -X POST https://api.warid.chat/api/v1/conversations/CONVERSATION_ID/messages \ -H "Authorization: Bearer mat_..." -H "Content-Type: application/json" \ -d '{"text": "Your order has shipped!"}' ``` [Send messages](https://warid.chat/docs/messaging) covers the 24-hour rule, images, WhatsApp templates and verification codes. The [API reference](https://warid.chat/docs/api/conversations) lists every endpoint. The OpenAPI document is at https://api.warid.chat/docs/json. ## 5. Open their dashboard To send your user into Warid already signed in, ask for a sign-in link when they click, then redirect them to it. A link works once and expires after two minutes, so make a new one on every click. ```bash curl -X POST https://api.warid.chat/api/v1/sign-in-links \ -H "Authorization: Bearer mat_..." -H "Content-Type: application/json" \ -d '{"path": "/inbox"}' ``` ```json { "url": "https://warid.chat/auth/link#token=msl_...", "expiresAt": "2026-10-15T12:02:00.000Z" } ``` `path` can be `/inbox` (the default), `/inbox/CONVERSATION_ID`, `/agent`, `/channels`, `/team` or `/developers`. The link signs in the person who connected your app. ## Full example A complete server in one file: the button, the callback, an API call and the dashboard link. Set `CLIENT_ID`, `CLIENT_SECRET` and `REDIRECT_URI` in the environment. ```js import express from "express"; import session from "express-session"; import { createHash, randomBytes } from "node:crypto"; const APP = "https://warid.chat"; const API = "https://api.warid.chat"; const { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } = process.env; const app = express(); app.use(session({ secret: "change-me", resave: false, saveUninitialized: true })); // 1. The "Connect with Warid" button links here. app.get("/warid/connect", (req, res) => { req.session.state = randomBytes(16).toString("hex"); req.session.verifier = randomBytes(32).toString("base64url"); const challenge = createHash("sha256").update(req.session.verifier).digest("base64url"); const url = new URL(`${APP}/oauth/authorize`); url.search = new URLSearchParams({ response_type: "code", client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, state: req.session.state, code_challenge: challenge, code_challenge_method: "S256", }); res.redirect(url.toString()); }); // 2. Warid sends the user back here. app.get("/warid/callback", async (req, res) => { if (req.query.error || req.query.state !== req.session.state) return res.status(400).send("Not connected"); const tokens = await fetch(`${API}/oauth/token`, { method: "POST", body: new URLSearchParams({ grant_type: "authorization_code", code: req.query.code, redirect_uri: REDIRECT_URI, code_verifier: req.session.verifier, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), }).then((r) => r.json()); req.session.warid = tokens; // Save with your user in your database. res.redirect("/"); }); // 3. Use the API. app.get("/warid/conversations", async (req, res) => { const data = await fetch(`${API}/api/v1/conversations`, { headers: { Authorization: `Bearer ${req.session.warid.access_token}` }, }).then((r) => r.json()); res.json(data.items); }); // 4. "Open Warid": straight into their dashboard. app.get("/warid/open", async (req, res) => { const link = await fetch(`${API}/api/v1/sign-in-links`, { method: "POST", headers: { Authorization: `Bearer ${req.session.warid.access_token}`, "Content-Type": "application/json" }, body: JSON.stringify({ path: "/inbox" }), }).then((r) => r.json()); res.redirect(link.url); }); app.listen(3000); ``` ```python import base64, hashlib, os, secrets import requests from flask import Flask, redirect, request, session APP = "https://warid.chat" API = "https://api.warid.chat" CLIENT_ID, CLIENT_SECRET, REDIRECT_URI = os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"], os.environ["REDIRECT_URI"] app = Flask(__name__) app.secret_key = "change-me" # 1. The "Connect with Warid" button links here. @app.get("/warid/connect") def connect(): session["state"] = secrets.token_hex(16) session["verifier"] = secrets.token_urlsafe(32) challenge = base64.urlsafe_b64encode(hashlib.sha256(session["verifier"].encode()).digest()).rstrip(b"=").decode() params = { "response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "state": session["state"], "code_challenge": challenge, "code_challenge_method": "S256", } return redirect(requests.Request("GET", f"{APP}/oauth/authorize", params=params).prepare().url) # 2. Warid sends the user back here. @app.get("/warid/callback") def callback(): if request.args.get("error") or request.args.get("state") != session.get("state"): return "Not connected", 400 tokens = requests.post(f"{API}/oauth/token", data={ "grant_type": "authorization_code", "code": request.args["code"], "redirect_uri": REDIRECT_URI, "code_verifier": session["verifier"], "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, }).json() session["meta_inbox"] = tokens # Save with your user in your database. return redirect("/") # 3. Use the API. @app.get("/warid/conversations") def conversations(): headers = {"Authorization": f"Bearer {session['meta_inbox']['access_token']}"} return requests.get(f"{API}/api/v1/conversations", headers=headers).json()["items"] # 4. "Open Warid": straight into their dashboard. @app.get("/warid/open") def open_dashboard(): headers = {"Authorization": f"Bearer {session['meta_inbox']['access_token']}"} link = requests.post(f"{API}/api/v1/sign-in-links", headers=headers, json={"path": "/inbox"}).json() return redirect(link["url"]) ``` ## Permissions | Scope | Lets your app | | --- | --- | | `conversations:read` | List conversations and read messages | | `conversations:write` | Send messages and notes; change status, assignee and tags | | `contacts:read` | Read contacts | | `contacts:write` | Rename contacts | | `channels:read` | See the connected WhatsApp, Instagram and Messenger accounts | | `agent:read` | Read the AI agent's settings and knowledge | | `agent:write` | Change the AI agent's instructions and knowledge | | `webhooks:read` | List webhook endpoints | | `webhooks:write` | Add and remove webhook endpoints | | `templates:read` | List WhatsApp message templates and their review status | | `templates:write` | Create and delete WhatsApp message templates | | `otp:send` | Send verification codes on WhatsApp and check them | | `dashboard:open` | Open the dashboard for the person who connected the app | ## Good to know - **Testing.** A new app connects only to your own workspaces. When it's ready for everyone, open it in **Developers** and click **Submit for review**. - **One app per workspace.** A workspace connects one app at a time. The business sees your app's activity in **Developers** and can disconnect it there; your tokens then return `401`. - **Training the AI agent** happens in the dashboard: businesses upload documents and write instructions in **AI agent**. Your app can link straight there with a sign-in link to `/agent`. - **Errors** are JSON like `{ "error": "insufficient_scope", "scope": "agent:write" }`. A `401` means the token expired (refresh it) or was revoked. Each connection gets 120 requests a minute. - **Events.** To hear about new messages instead of polling, add [webhooks](https://warid.chat/docs/webhooks). --- # Send messages Message customers on WhatsApp, Instagram and Messenger, manage WhatsApp templates, send campaigns and login codes. Everything here uses the same token as the rest of the API (a workspace key `mik_…` or an app's `mat_…`). Find the channel ids with `GET https://api.warid.chat/api/v1/channels`. ## The 24-hour rule WhatsApp, Instagram and Messenger only accept normal messages (text and images) within **24 hours of the customer's last message**. After that: - **WhatsApp:** send an approved **template**. Templates are also the only way to message someone first. - **Instagram and Messenger:** wait until the customer writes again. If the window is closed, the API answers `422` with `"error": "window_closed"` and sends nothing. Each conversation's `windowExpiresAt` tells you when it closes. ## Send a message Reply in a conversation with text, an image (a public JPEG or PNG link, up to 5 MB), or both: ```bash curl -X POST https://api.warid.chat/api/v1/conversations/CONVERSATION_ID/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"text": "Here is your receipt", "imageUrl": "https://yourapp.com/receipts/1042.png"}' ``` Or skip looking up the conversation. Send to a **contact** on any channel, or to a **phone number** on a WhatsApp channel: ```bash # A contact (their latest conversation, or the one on channelId) curl -X POST https://api.warid.chat/api/v1/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"contactId": "CONTACT_ID", "text": "Your table is ready"}' # A phone number, first message on WhatsApp: use a template curl -X POST https://api.warid.chat/api/v1/messages \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "to": "+9647701234567", "template": {"name": "order_ready", "language": "en_US", "params": ["Sara", "#1042"]}}' ``` The response is the stored message. `status` is `sent`, then moves to `delivered` and `read` (listen for `message.updated` [webhooks](https://warid.chat/docs/webhooks)); `failed` comes with an `error`. Messages from the API show in the inbox and don't pause the AI agent. ## WhatsApp templates A template is a message Meta approved in advance. Templates belong to the WhatsApp account behind a number, so numbers on one account share them. Businesses can also manage them in **Channels → Templates**. **List them** (scope `templates:read`). `params` is how many values a send needs, one per entry in `vars`: the body's blanks, then the title's, then each link button's; only `APPROVED` ones can be sent. ```bash curl https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates -H "Authorization: Bearer $META_INBOX_KEY" ``` ```json [ { "id": "1273849204", "name": "order_ready", "language": "en_US", "category": "UTILITY", "status": "APPROVED", "rejectedReason": null, "body": "Hi {{1}}, your order {{2}} is ready.", "params": 2, "vars": [{ "key": "1", "part": "body" }, { "key": "2", "part": "body" }], "header": null, "footer": null, "buttons": [] } ] ``` **Create one** (scope `templates:write`). Put `{{1}}`, `{{2}}`, … where values go and give an example for each: ```bash curl -X POST https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{ "name": "order_ready", "language": "en_US", "category": "UTILITY", "body": "Hi {{1}}, your order {{2}} is ready.", "examples": ["Sara", "#1042"], "buttons": [{ "type": "url", "text": "Track order", "url": "https://yourapp.com/orders" }] }' ``` - `name`: lowercase letters, numbers and underscores. - `category`: `UTILITY` (orders, bookings, reminders), `MARKETING` (offers, news) or `AUTHENTICATION` (login codes, see below). - Optional: `header` and `footer` (short text lines), up to 10 `buttons` (`quick_reply`, `url` or `phone`). It starts as `PENDING`. Meta usually reviews it within minutes; then it's `APPROVED` or `REJECTED` with a `rejectedReason`. Check with `GET …/templates?name=order_ready`, or subscribe a webhook to `template.updated`. **Delete one** with `DELETE https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates/order_ready`. It's removed in every language, and Meta blocks the name for 30 days. ## Campaigns Send one approved WhatsApp template to many customers, now or later, and follow how far each message got. Scopes `campaigns:write` to send and `campaigns:read` for results. WhatsApp channels only: Instagram and Messenger don't allow marketing broadcasts. Businesses can also do all of this from **Campaigns** in the dashboard. ```bash # Everyone tagged vip, with their name in {{1}} curl -X POST https://api.warid.chat/api/v1/campaigns \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "name": "Eid sale", "template": {"name": "eid_offer", "language": "en"}, "params": [{"field": "name", "fallback": "there"}, {"text": "EID20"}], "audience": {"type": "tags", "tags": ["vip"]}}' ``` - **`audience`**: `{ "type": "tags", "tags": [...] }` (conversations on that number with any of the tags), `{ "type": "everyone" }` (everyone who chatted on it), or `{ "type": "list", "recipients": [{ "phone": "+9647701234567", "name": "Sara", "values": { "code": "SARA10" } }] }` for your own list, up to 10,000 people. - **`params`**: one per entry in the template's `vars`, in order. `{ "text": "EID20" }` is the same for everyone; `{ "field": "name" }` is the customer's name and `{ "field": "code" }` reads their `values.code`. Add a `fallback` for people without one, or they're skipped. - **`headerMedia`**: `{ "link": "https://…" }` when the template has a picture, video or file on top (`headerFormat` in the template list). - **`scheduledAt`**: an ISO time up to 30 days ahead. Leave it out to send now. Customers who reply **STOP** (or *إيقاف*) are opted out and skipped from then on; **START** opts them back in. You can set it yourself with `PATCH /contacts/{id}` and `{ "optedOut": true }`. Messages go out at about 20 a second and land in each customer's conversation, so replies show up in the inbox. Follow along with `GET https://api.warid.chat/api/v1/campaigns/CAMPAIGN_ID`: ```json { "id": "…", "status": "sent", "stats": { "total": 1200, "queued": 0, "sent": 1180, "delivered": 1150, "read": 820, "replied": 96, "failed": 12, "skipped": 8 } } ``` `status` is `scheduled`, `sending`, `paused` (out of credit: `error` is `no_credit`, and it carries on by itself after a top-up), `sent`, `canceled`, or `failed` when WhatsApp stopped it early (`error` has its code, e.g. `132015` for a paused template). `GET …/campaigns/CAMPAIGN_ID/recipients?status=failed` lists people with a `reason`: `opted_out`, `invalid_phone`, `missing_value`, `canceled`, or WhatsApp's code (`131049` means WhatsApp held back a marketing message to that person). `POST …/campaigns/CAMPAIGN_ID/cancel` stops one that hasn't finished. Subscribe a webhook to `campaign.updated` to hear when it starts and finishes. ## Verification codes (OTP) Send a one-time code on WhatsApp and check what the user types. We make the code, send it, keep only a hash, and expire it. Scope `otp:send`, WhatsApp channels only. **1. Create the template once.** Meta writes the text ("123456 is your verification code.") and adds a **Copy code** button: ```bash curl -X POST https://api.warid.chat/api/v1/channels/CHANNEL_ID/templates \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"name": "login_code", "language": "en", "category": "AUTHENTICATION", "codeExpiresMinutes": 10}' ``` Wait until it's `APPROVED`. (Or click **Verification codes** in **Channels → Templates**.) **2. Send a code** when the user asks to sign in: ```bash curl -X POST https://api.warid.chat/api/v1/otp \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"channelId": "CHANNEL_ID", "to": "+9647701234567"}' ``` ```json { "id": "6f1c2a7e-0b7d-4e57-9a51-2f0c9e3b8d11", "to": "+9647701234567", "channelId": "CHANNEL_ID", "expiresAt": "2026-10-16T12:10:00.000Z", "template": { "name": "login_code", "language": "en" } } ``` Options: `length` (4–8 digits, default 6), `expiresInMinutes` (1–30, default 10), and `template` (`{ "name": "login_code", "language": "ar" }`) to pick one when you have several. The first approved authentication template is used otherwise. **3. Check the code** the user typed: ```bash curl -X POST https://api.warid.chat/api/v1/otp/verify \ -H "Authorization: Bearer $META_INBOX_KEY" -H "Content-Type: application/json" \ -d '{"to": "+9647701234567", "code": "482913"}' ``` ```json { "valid": true, "id": "6f1c2a7e-0b7d-4e57-9a51-2f0c9e3b8d11", "to": "+9647701234567" } ``` A wrong or old code returns `200` with `"valid": false` and a `reason`: `wrong_code` (with `attemptsLeft`), `expired`, `already_used`, `too_many_attempts` or `not_found`. Sign the user in only when `valid` is `true`. **Rules that keep it safe:** - Each code works once. Sending a new code to a number ends the previous one. - 5 wrong tries end a code; send a new one. - Per number: one code every 30 seconds and 5 an hour. Over that, `429` with `otp_too_soon` or `otp_rate_limited` and a `Retry-After` header (seconds). - Codes aren't shown in the inbox. ## Errors | Status | `error` | What to do | | --- | --- | --- | | 422 | `window_closed` | More than 24 hours since the customer wrote. On WhatsApp send a `template`. | | 422 | `template_not_found` | No approved template with that name and language. | | 422 | `template_params_mismatch` | Send exactly `params` values, none empty. | | 422 | `templates_need_whatsapp` | Templates and codes only work on official WhatsApp channels. | | 422 | `template_rejected` | Meta refused the new template; `message` says why (often a duplicate name). | | 422 | `examples_must_match_params` | Give one example per `{{n}}` in the body. | | 422 | `otp_template_missing` | Create an approved `AUTHENTICATION` template first. | | 422 | `invalid_phone` | Use international format, e.g. `+9647701234567`. | | 422 | `no_conversation` | The contact has no conversation (on that channel). | | 422 | `no_recipients` | Nobody in the audience can get it (all opted out, invalid or missing a value). | | 422 | `header_media_required` | The template has a picture, video or file on top; send `headerMedia.link`. | | 409 | `campaign_finished` | The campaign already finished, so it can't be canceled. | | 429 | `otp_too_soon`, `otp_rate_limited` | Wait `Retry-After` seconds. | | 502 | `send_failed`, `whatsapp_unavailable` | WhatsApp didn't accept the request; `message` has its reason when there is one. | --- # Webhooks Get new messages and changes posted to your server as they happen. Add an endpoint in **Developers → Webhooks**, or from your app with `POST /api/v1/webhooks` (scope `webhooks:write`). We POST each event to it as JSON. | Event | When | | --- | --- | | `message.created` | A customer wrote, or a message was sent | | `message.updated` | A message was delivered, read or failed | | `conversation.updated` | Status, assignee, tags or the AI switch changed | | `channel.updated` | A channel connected or needs reconnecting | | `template.updated` | Meta approved, rejected or paused a WhatsApp template (`data.template.status`) | | `campaign.updated` | A campaign started, finished, was canceled or stopped (`data.campaign.status`, `data.campaign.stats`) | | `conversation.handoff` | The AI agent passed a chat to the team (`data.reason` says why, `data.conversation` is the chat) | | `knowledge.updated` | A knowledge source finished indexing (`data.source.status` is `ready` or `failed`) | ```json { "id": "evt_8f2c...", "type": "message.created", "createdAt": "2026-10-15T12:00:00.000Z", "workspaceId": "b5d3d605-...", "data": { "message": { "id": "...", "direction": "inbound", "body": "Hi!" } } } ``` ## Check the signature Every delivery has an `X-Inbox-Signature: t=…,v1=…` header. `v1` is the HMAC-SHA256 of `.`, keyed with the endpoint's secret. Try it: ```js import { createHmac, timingSafeEqual } from "node:crypto"; function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected)); } ``` ```python import hmac, hashlib, time def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts["t"])) > 300: return False expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(parts["v1"], expected) ``` ## Retries Answer with any 2xx within 10 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 8 hours. Deliveries can repeat, so skip event ids you've already handled. --- # AI assistants (MCP) Work in your inbox from Claude, ChatGPT, Cursor and other assistants. Warid is an MCP server. Add one URL to your assistant, approve it, and it can read and answer conversations for you. On this page and on the dashboard's Developers screen, each assistant has a button with a short walkthrough. Cursor and VS Code open straight on their install prompt. ```text https://api.warid.chat/mcp ``` Sign in is OAuth with dynamic client registration, so there's no key to paste. Every assistant ends on a Warid page where you pick the workspace and press **Allow**. Add it in your assistant: ```text Customize → Connectors → Add → Add custom connector Name: Warid URL: https://api.warid.chat/mcp (Team and Enterprise: an Owner adds it in Organization settings → Connectors) ``` ```text chatgpt.com/plugins → + → Add custom MCP server Name: Warid Server URL: https://api.warid.chat/mcp Authentication: OAuth Tick "I understand", then Create as a plugin (Business and Enterprise: an admin adds it. Pro gets read-only tools.) ``` ```bash claude mcp add --transport http warid https://api.warid.chat/mcp # then in claude: /mcp → warid → Authenticate ``` ```json { "mcpServers": { "warid": { "url": "https://api.warid.chat/mcp" } } } ``` ```json { "servers": { "warid": { "type": "http", "url": "https://api.warid.chat/mcp" } } } ``` ```json { "mcpServers": { "warid": { "serverUrl": "https://api.warid.chat/mcp" } } } ``` ```bash codex mcp add warid --url https://api.warid.chat/mcp codex mcp login warid ``` ```bash gemini mcp add --transport http warid https://api.warid.chat/mcp # then in gemini: /mcp auth warid ``` Cursor's config lives in `~/.cursor/mcp.json`, VS Code's in `.vscode/mcp.json`, and Windsurf's in `~/.codeium/windsurf/mcp_config.json` (Settings → Cascade → Manage MCPs → View raw config). Connected assistants show under **Developers → Connected to this workspace**, where you can disconnect them. They don't take your workspace's connected-app slot.