Authentication & keys
One API key is your project and your tenant boundary. There are two ways to get one: mint an anonymous key instantly for free channels, or sign in once with Google to unlock paid channels and claim $100 in free credit. Store it as COMM_API_KEY and the SDK does the rest.
The key is the tenant boundary
Everything the SDK does authenticates with a single bearer key. The SDK reads COMM_API_KEY (and COMM_BASE_URL) from the environment or a ./.env file next to your code, and sends it as Authorization: Bearer <key> on every request.
One key maps to exactly one project. Every connection, conversation, and message lives under that project and is isolated from every other key's data. Keep the key server-side: anyone holding it can send and read on your agent's channels.
COMM_API_KEY=comm_sandbox_xxxxxxxx
COMM_BASE_URL=https://api.trycaspianai.comPath 1: Anonymous key (free, no signup)
Free channels need no account: email, Telegram, Discord, Slack, Instagram, Facebook, and your own Twilio/Telnyx number (SMS) or your own Twilio WhatsApp. Mint a sandbox key with a single unauthenticated call:
curl -s -X POST https://api.trycaspianai.com/v1/projects/sandbox \
-H 'Content-Type: application/json' -d '{"name":"my-agent"}'It returns a project and its key:
{ "project_id": "proj_...", "api_key": "comm_sandbox_..." }Write api_key into .env as COMM_API_KEY and start building. No SDK method is needed for this step: the key has to exist before the client can construct.
A comm_sandbox_ key works on every free channel forever. You only need Path 2 if you want a paid channel, a dashboard, or credit.
Path 2: Sign in with Google (paid channels + $100 credit)
Paid channels (X, WhatsApp on Caspian's Meta number, and iMessage) run on Caspian's own network and require a one-time sign-in. Signing in also gives you a dashboard and $100 in free credit on first sign-in. It uses the OAuth 2.0 device flow, so your agent never handles a password.
Step 1: Start the device flow
Call /v1/auth/device/start. Pass your current anonymous api_key so the sandbox project (and everything you already connected) carries over into the signed-in account instead of starting fresh.
curl -s -X POST https://api.trycaspianai.com/v1/auth/device/start \
-H 'Content-Type: application/json' \
-d '{"api_key":"comm_sandbox_xxxxxxxx"}'You get back a verification link and a device code to poll with:
{
"user_code": "WXYZ-2345",
"device_code": "dev_...",
"verification_uri_complete": "https://api.trycaspianai.com/device?code=WXYZ-2345",
"interval": 5
}Step 2: Show the user the link and wait
Print verification_uri_complete for the developer and stop. They open it, sign in with Google, and approve. That browser step is the whole authentication.
Sign in to Caspian (you get $100 free credit):
https://api.trycaspianai.com/device?code=WXYZ-2345Step 3: Poll for the key
Poll /v1/auth/device/token with the device_code every interval seconds. While the user hasn't finished it returns {"status":"pending"}; once they sign in it returns approved with the upgraded key.
curl -s -X POST https://api.trycaspianai.com/v1/auth/device/token \
-H 'Content-Type: application/json' \
-d '{"device_code":"dev_..."}'{
"status": "approved",
"api_key": "comm_...",
"base_url": "https://api.trycaspianai.com"
}Write the returned api_key back to .env. Future runs reuse it and never sign in again.
The whole flow in code
The device flow lives at the HTTP layer, so drive it with plain requests and then hand the resulting key to CommClient. Both examples poll until approved, then construct the client with the new key.
import time, httpx
from caspian_sdk import CommClient
BASE = "https://api.trycaspianai.com"
# Pass the current sandbox key so the project carries over.
start = httpx.post(f"{BASE}/v1/auth/device/start",
json={"api_key": "comm_sandbox_xxxxxxxx"}).json()
print("Sign in to Caspian (you get $100 free credit):")
print(start["verification_uri_complete"])
# Poll until the user signs in with Google in the browser.
while True:
time.sleep(start["interval"])
res = httpx.post(f"{BASE}/v1/auth/device/token",
json={"device_code": start["device_code"]}).json()
if res["status"] == "approved":
api_key = res["api_key"]
break
# Same handler, now on a signed-in key that can reach paid channels.
client = CommClient(api_key=api_key, base_url=BASE)
client.connect_imessage()
@client.on_message
def handle(message):
message.reply(f"You said: {message.text}")
client.listen()import { CommClient } from "caspian-sdk";
const BASE = "https://api.trycaspianai.com";
const post = (path: string, body: unknown) =>
fetch(BASE + path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then((r) => r.json());
// Pass the current sandbox key so the project carries over.
const start = await post("/v1/auth/device/start", { api_key: "comm_sandbox_xxxxxxxx" });
console.log("Sign in to Caspian (you get $100 free credit):");
console.log(start.verification_uri_complete);
// Poll until the user signs in with Google in the browser.
let apiKey: string;
for (;;) {
await new Promise((r) => setTimeout(r, start.interval * 1000));
const res = await post("/v1/auth/device/token", { device_code: start.device_code });
if (res.status === "approved") { apiKey = res.api_key; break; }
}
// Same handler, now on a signed-in key that can reach paid channels.
const client = new CommClient({ apiKey, baseUrl: BASE });
await client.connectImessage();
client.onMessage(async (message) => {
await message.reply(`You said: ${message.text}`);
});
await client.listen();Because you passed the sandbox api_key to device/start, the connections you built while anonymous keep working under the same project after sign-in. Nothing to re-connect.
Free vs paid at a glance
| Channel | Tier | Key needed |
|---|---|---|
| free | Anonymous sandbox key | |
| Telegram | free | Anonymous sandbox key |
| Discord | free | Anonymous sandbox key |
| Slack | free | Anonymous sandbox key |
| Instagram / Facebook | free | Anonymous sandbox key |
| SMS (your Twilio/Telnyx number) | free | Anonymous sandbox key |
| WhatsApp (your own Twilio) | free | Anonymous sandbox key |
| X (Twitter) | paid | Signed-in key |
| WhatsApp (Caspian's Meta number) | paid | Signed-in key |
| iMessage | paid | Signed-in key |
Connecting a paid channel with an anonymous key returns 402 Payment Required with a message telling you to sign in. Run Path 2 above, swap in the signed-in key, and retry (no other change to your code).