API reference

Use your own chat screen (API)

Already have support chat inside your app? Keep it. Your server sends each customer message to Custard AI and shows the reply in your own design. Same staff, same knowledge, rules, live lookups and support cases as every other door, and every chat shows up in your Inbox.

How it fits together

Your app talks to your server, your server talks to Custard AI. Your server already knows who is signed in, so it passes that customer in user. Your staff greet them by name, never ask who they are, and the userId is attached to every live lookup, so "what is my balance" or "where is my transfer" reaches your lookup link for exactly that customer.

1. Get your key

In Rules & connections, under Business connections, tap Show API key. Keep it on your server only, never inside a website or phone app. Also switch on On your website for the staff member; that switch turns API replies on and off too.

2. Send a message

Send only the new message. We keep the history, so pass back the conversationId from the last answer to carry on, or null to start fresh.

One session for every channel

Create one session with the staff member and the channels you want (chat, voice, video). You get back everything each channel needs. Abilities such as live lookup, live changes and identity checks come from that staff member's switches, so you never pass them yourself. One key, one error format, one signed webhook stream. Only channels switched on for that staff member come back; the rest are listed in refused. Send chat messages to the chat url, open a WebSocket to the voice endpoint with the session token (it expires in 5 minutes, so mint one per call), and show video by putting the embedUrl in an iframe that allows microphone and camera. The chat and voice addresses below still work as shortcuts.

// One API, one object. On your server:
curl https://custardai.dev/api/public/v1/sessions \
  -H "Authorization: Bearer csk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "YOUR_STAFF_ID", "channels": ["chat", "voice", "video"],
        "user": { "userId": "u_123", "email": "ana@example.com" } }'

// Answer
{ "ok": true, "object": "session", "agentId": "...", "staff": "Sara",
  "channels": {
    "chat":  { "url": "https://custardai.dev/api/public/v1/chat", "conversationId": null },
    "voice": { "session": "vss_...", "endpoint": "wss://custardai.dev/api/live?session=1", "expiresIn": 300 },
    "video": { "embedUrl": "https://custardai.dev/embed/YOUR_STAFF_ID?card=1", "via": "embed" }
  },
  "abilities": ["live_lookup", "verify_identity"], "refused": [], "expiresIn": 300 }

// Every /v1 error looks the same:
{ "ok": false, "error": "staff_paused",
  "error_detail": { "type": "invalid_request_error", "code": "staff_paused", "message": "That staff member is paused" } }
curl https://custardai.dev/api/public/v1/chat \
  -H "Authorization: Bearer csk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "YOUR_STAFF_ID",
    "message": "Where is my transfer from this morning?",
    "conversationId": null,
    "user": { "userId": "u_123", "email": "ana@example.com", "fullName": "Ana Silva" }
  }'

// Answer
{ "ok": true, "reply": "Hi Ana, let me check that for you...", "conversationId": "7f1c..." }

// Voice from your own mic button (no widget). On your server:
curl https://custardai.dev/api/public/v1/voice/session \
  -H "Authorization: Bearer csk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "YOUR_STAFF_ID", "user": { "userId": "u_123", "email": "ana@example.com" } }'
// Answer: { "ok": true, "session": "vss_...", "endpoint": "wss://custardai.dev/api/live?session=1", "expiresIn": 300 }
// In your app: open the endpoint, create a WebRTC offer with the mic track, then send
// { "type": "app.start", "mode": "headless", "session": "vss_...", "agentId": "YOUR_STAFF_ID", "sdp": offer.sdp, "token": "" }
// Apply the SDP answer you receive, send { "type": "app.ready" } once audio plays, and { "type": "session.close" } to hang up.

// Product Copilot in your own app (chat and voice). Send the screens and buttons staff may use:
//   chat:  add "guide": { "pages": ["Transfer history"], "spots": ["Send money button"] } and "screen": { "id": "home", "title": "Home" }
//          the answer adds "guide": [{ "action": "highlight", "label": "Send money button" }]
//   voice: add the same "guide" when you create the session; when the customer changes screen send
//          { "type": "app.screen", "screen": "transfer_history", "title": "Transfer history" }
//          and listen for { "type": "app.guide", "action": "open_page" | "highlight", "label": "..." }
// Your app then opens that screen or shows its own spotlight. Staff only ever pick from your labels.

Optional attachment: { base64, mime, filename } for a photo or PDF up to about 3 MB, such as a receipt. Errors: 401 wrong key, 404 staff not in your business, 409 staff switched off, 422 message refused (too long, out of credit, chat too long). A chat idle for 3 hours closes; send a fresh one with conversationId null.

3. With React

The package has a server helper that adds your key, and a hook for your existing chat screen.

// Your server (Next.js route, Express, anything). The key never leaves it.
import { createCustardClient } from "@custardai/react";
const custard = createCustardClient({ apiKey: process.env.CUSTARD_API_KEY });

export async function POST(req) {
  const user = await getSignedInUser(req);          // your own login check
  const { message, conversationId } = await req.json();
  const result = await custard.chat({
    agentId: "YOUR_STAFF_ID",
    message,
    conversationId,                                  // null starts a new chat
    user: { userId: user.id, email: user.email, fullName: user.name, tier: user.tier },
  });
  return Response.json(result);
}
// Your existing chat screen
import { useCustardChat } from "@custardai/react";

function SupportChat() {
  const { messages, send, sending, error } = useCustardChat({ endpoint: "/api/support/chat" });
  return (
    <YourChatUI
      messages={messages}          // [{ role: "user" | "assistant", text }]
      onSend={(text) => send(text)}
      typing={sending}
      error={error}
    />
  );
}

Good to know

Connect live lookups (step 5) so staff can read balances, transaction status and order details from your system. Add a line to the staff instructions such as "Never ask for PINs, passwords, card numbers or one-time codes." Changes only happen if you switch on Live changes and the customer clearly says yes; for read-only support leave it off. Each reply is charged like a website chat reply, and the webhook in step 4 still receives the summary.

Stuck?

Write to hello@custardai.dev and we will help you connect it.