How to add AI agents to your SaaS without building an agent platform
A practical guide to giving every customer their own AI agent: user-scope API keys, per-user agents, end-user isolation, streaming, files and usage-based billing with the FastClaw /v1 API.
"Let each customer have their own AI assistant" sounds like a feature. In practice it is a platform: per-customer agents, isolated history and memory, a sandbox for tools, streaming, file outputs and a way to know what each customer cost you.
This guide shows how to get there by treating an agent runtime as a backend service. Your app keeps owning users, permissions and billing; FastClaw runs the agents. All examples use the public /v1 API — the same one documented in every FastClaw install at /skills/agent-integration/SKILL.md.
The division of labour
Get this right and everything else is plumbing:
- Your app owns sign-in, which user may use which agent, your own chat records, and billing.
- FastClaw owns agent configuration, the agent loop, tools and sandbox, sessions, memory and usage metering.
FastClaw never calls your app back and never needs to know your users' emails. Your backend calls FastClaw; browsers and mobile apps only ever talk to your backend.
Step 1: configure the connection
You need two server-side settings:
FASTCLAW_BASE_URL=https://claw.example.com # no trailing slash
FASTCLAW_API_KEY=fc_... # from Console → API Keys
Check them before writing any more code:
curl -s "$FASTCLAW_BASE_URL/v1/agents" \
-H "Authorization: Bearer $FASTCLAW_API_KEY"
A 200 lists the agents the key can use; 401 unauthorized means a wrong or revoked key.
Step 2: pick the right key type
FastClaw has two key types that matter for integrations:
- Agent keys are granted specific, existing agents. Use them when your app talks to a fixed set of agents — a support bot, an image editor.
- User keys reach every agent of their FastClaw account and can create and delete agents. Use them when your app creates agents on demand, for example one per customer or per workspace.
For the "every customer gets an agent" pattern you want a user key — on a FastClaw account dedicated to this integration, and one per environment (myapp-prod, myapp-dev), so the key can never touch agents used for anything else.
Step 3: create an agent per customer
When a customer turns the feature on, create their agent and store the mapping in your own database:
const res = await fetch(`${process.env.FASTCLAW_BASE_URL}/v1/agents`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FASTCLAW_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Acme Assistant',
description: 'Answers questions about Acme projects',
instructions: 'You are a concise assistant for the Acme team.',
metadata: { workspace_id: 'ws_123' },
}),
});
const { agent } = await res.json();
// save agent.id next to ws_123 in your database
A few details worth knowing:
- Only
nameis required.instructionsbecomes the agent'sSOUL.md. modeltakesprovider/model; leave it out to use the account default. You can change it later withPATCH /v1/agents/{id}— see Models.metadatais yours: up to 16 string keys, filterable withGET /v1/agents?metadata[workspace_id]=ws_123. It is never used for authorization.- Richer identity files (
IDENTITY.md,AGENTS.md,KNOWLEDGE.md, …) can be written withPUT /v1/agents/{id}/system-files/{name}.
If your product has one shared assistant rather than one per customer, skip this step and create the agent once in the console. The rest of the guide is identical.
Step 4: chat, with the end-user set
This is the call your product makes on every message:
const res = await fetch(
`${process.env.FASTCLAW_BASE_URL}/v1/chat/completions`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FASTCLAW_API_KEY}`,
'Content-Type': 'application/json',
'X-Fastclaw-Session-Key': `myapp:${conversationId}`,
'X-Fastclaw-End-User': currentUser.id,
},
body: JSON.stringify({
agent_id: agentId,
stream: true,
messages: [{ role: 'user', content: userMessage }],
}),
}
);
Three fields do the heavy lifting:
agent_idis strict. If the key may not use that agent you get404 agent_not_found— never a reply from some other agent.X-Fastclaw-Session-Keyselects the conversation history. Make it deterministic and prefixed with your app name. Without it, every call starts a fresh session.X-Fastclaw-End-User(or theuserbody field) is your user's stable ID. It keeps that user's session history and personal memory (USER.md,MEMORY.md) private to them while they share the agent's persona and skills. Leave it out when everybody should share one memory — for example a user's own private agent.
Send only the newest user message. FastClaw keeps the history itself, keyed by the session key.
Before every call, your backend must check that the signed-in user is allowed to use that agent. FastClaw isolates accounts and keys from each other; it does not know your permission model.
Step 5: handle the stream
The response is standard OpenAI-style server-sent events, so most OpenAI client libraries can parse it. Two FastClaw additions make for a better UI:
- Tool progress. When the agent starts or finishes a tool call you receive a chunk with an empty delta and a top-level
toolobject —namesuch asexecorread_file, andstatusofrunningordone. Show it as a progress step in your own words. - Errors are errors. If the model API fails, a stream ends with an
errorobject on its final chunk (non-streaming requests get a502withcode: "agent_error"). Treat it as "nothing was produced": do not charge, offer a retry.
All /v1 errors share one shape, so you can branch on error.code: invalid_request, unauthorized, forbidden, agent_quota_exceeded, agent_not_found, payment_required, rate_limited and so on.
Step 6: return files the agent produced
Agents that edit images or generate reports produce files. FastClaw returns them in a files array — at the top level, or on the final streaming chunk — each with a url you download using the same Authorization and X-Fastclaw-End-User headers. End-users can only read files from their own conversations. For a single small image, send "return_files": "inline" and you get a data_url instead.
Because tools for API conversations always run in the sandbox, a customer asking the agent to "run this script" never touches your FastClaw host.
Step 7: meter and bill
Every reply includes usage summed across every model call in the turn, tool loops included: OpenAI-style prompt_tokens and completion_tokens, plus cache read and write counts and model_calls. Record it per turn against your customer.
For dashboards, GET /v1/usage returns daily rows filterable by agent_id and end_user, and /v1/quota lets you cap monthly tokens or requests for an end-user. If you run FastClaw for others, the admin-side billing hooks (usage export and billing hold) let an external billing system pause an account; its API calls then return 402 payment_required.
A launch checklist
FASTCLAW_BASE_URLandFASTCLAW_API_KEYlive in server-side config only.- A user key on a dedicated account per environment.
- Your app checks access to the agent before every call.
- A deterministic session key per conversation, and the end-user set whenever history must stay private.
- SSE parsing with tool progress and
error.codehandling. - Files taken from
files, never from guesses about paths. - Usage stored per turn.
Where to go next
The SaaS builders use case shows how these pieces fit into a product. If your customers live in chat apps rather than your web UI, the same agents can answer in Slack or Telegram via channels. To decide between self-hosting FastClaw and the managed option, see pricing.