Developer PlatformBuild your product on the governed control layer behind Knowledge Spaces: isolated tenants, scoped retrieval, and audited model calls out of the box. Register with your organization name, connect your model key, and make your first API call in minutes.
$ curl -X POST https://spaces.sprinklenet.com/api/bots/<bot_id>/chat \ -H "Content-Type: application/json" \ -H "X-API-Key: ks_live_••••••••" \ -d '{ "session_id": "demo-001", "text": "What should I read first?" }' 200 OK { "success": true, "text": "Start with the implementation brief..." } # a 200 means your organization key and bot are working.
Sign up with your organization name, grab the Organization API Key we provision for you, then make your first bot call. No long setup, and no sales call required to start.
Register with an organization name and Knowledge Spaces creates your initial workspace.
Free access runs on your provider key. Add OpenAI, Anthropic, Gemini, DeepSeek, or another supported model.
We create your organization and provision its API Key. Copy it from your console, keep it in your secret store, and regenerate anytime.
Move to paid usage when you need higher limits, managed models, or production support.
Connect a model provider key, generate your Organization API Key, then point it at a bot and get an answer grounded in your content.
curl -X POST https://spaces.sprinklenet.com/api/bots/<bot_id>/chat \
-H "Content-Type: application/json" \
-H "X-API-Key: ks_live_••••••••" \
-d '{ "session_id": "demo-001", "text": "What is our refund policy?" }'
const res = await fetch(
"https://spaces.sprinklenet.com/api/bots/<bot_id>/chat",
{
method: "POST",
headers: {
"X-API-Key": "ks_live_••••••••",
"Content-Type": "application/json",
},
body: JSON.stringify({ session_id: "demo-001", text: "What is our refund policy?" }),
}
);
const data = await res.json();
import requests
res = requests.post(
"https://spaces.sprinklenet.com/api/bots/<bot_id>/chat",
headers={"X-API-Key": "ks_live_••••••••"},
json={"session_id": "demo-001", "text": "What is our refund policy?"},
)
print(res.json())
Your account starts with an organization workspace. As you grow, managed organizations can separate bots, knowledge spaces, and data by customer, brand, or environment.
Organization keys, rate limits, sessions, and bots are scoped by organization, so one customer can never reach another's data.
Everything you need to run AI knowledge agents for many tenants in production.
Organizations and managed child organizations keep each customer's data, bots, and spaces isolated.
One hashed organization key for bot chat and session APIs. Regenerate it from Settings when access needs to rotate.
Create, continue, inspect, update, end, and delete conversation sessions through the public API.
Track answer usage by organization and bot. Plans scale from free BYOK to managed model access.
Bring your own OpenAI, Anthropic, Gemini, DeepSeek, or AWS-connected model access, or run on managed models.
Every call is tenant-scoped, retrieval-bounded, and logged. It is the same control layer documented in our security white paper.
Ground answers in your own content and serve a different assistant to each customer, team, or product.
Answer from your help center, docs, and policies, with one isolated workspace per customer.
Answer from runbooks, wikis, and handbooks, with each team's assistant scoped to its own Spaces.
Embed an assistant grounded in your documentation, in your app or your site.
Three end-to-end walkthroughs, checked against the public OpenAPI spec. Each one goes from zero to a working call, with the response you should see and the fix for the most common failure.
Register, generate your org API key in the console, and get a bot answer with your organization's governance (tenant isolation, logged calls) applied automatically.
Sign up at spaces.sprinklenet.com/register with your organization name to create your workspace, then copy (or generate) your Organization API Key in Console → Settings → API Keys. It's shown once, so store it in a secret manager now.
export KS_API_KEY="ks_live_your_key_here"
export BOT_ID="665f1a2b3c4d5e6f7a8b9c0d" # from your bot in the console
One POST with a session_id you control and the user's text.
curl -X POST "https://spaces.sprinklenet.com/api/bots/$BOT_ID/chat" \
-H "Content-Type: application/json" \
-H "X-API-Key: $KS_API_KEY" \
-d '{ "session_id": "'"$(uuidgen)"'", "text": "What should I read first?" }'
200 OK { "success": true, "text": "Start with the implementation brief..." }
That call ran with your org's governance applied: the key is scoped to a single organization, so it can only reach bots and sessions your org owns, and the call is tenant-scoped and logged. Want tokens as they generate? Add "stream": true and the response becomes a text/event-stream of Server-Sent Events.
Every response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. The default policy is 100 requests per 15 minutes per key; GET /rate-limit/info returns the live policy without authentication.
The key is missing, truncated, or under the wrong header. Send the full ks_live_... value as X-API-Key (or Authorization: Bearer). Lost keys can't be recovered (only a hash is stored); regenerate in the console. A 401 with a key you know is right usually means the bot id and the key belong to different organizations: a key only reaches bots its own org owns, which is the isolation working as designed. A 404 means the bot id itself doesn't exist; re-copy it from the console.
Keep one conversation across many calls, then read the transcript and manage the session's lifecycle.
session_id is caller-controlled: reuse a value to continue a thread, use a new value to start fresh. A predictable id (an email, a row number) risks one user's messages landing in another user's thread inside your product, so mint a UUID per conversation.
const sessionId = crypto.randomUUID();
// e.g. "6b3f2c9e-8a41-4d2f-9c57-1e2a7b3d4f80"
The second call answers with the earlier turns in context.
const BASE = "https://spaces.sprinklenet.com/api";
const headers = {
"X-API-Key": process.env.KS_API_KEY,
"Content-Type": "application/json",
};
await fetch(`${BASE}/bots/${process.env.BOT_ID}/chat`, {
method: "POST",
headers,
body: JSON.stringify({
session_id: sessionId,
text: "My name is Dana. Which plan fits a two-person team?",
}),
});
const followUp = await fetch(`${BASE}/bots/${process.env.BOT_ID}/chat`, {
method: "POST",
headers,
body: JSON.stringify({ session_id: sessionId, text: "What was my name again?" }),
});
console.log(await followUp.json());
// { "success": true, "text": "You said your name is Dana." }
const history = await fetch(
`${BASE}/bots/${process.env.BOT_ID}/chat?session_id=${sessionId}`,
{ headers: { "X-API-Key": process.env.KS_API_KEY } }
);
console.log(await history.json());
200 OK { "success": true, "messages": [ { "role": "user", "content": "My name is Dana. Which plan...", "timestamp": "2026-07-08T14:03:12.000Z" }, { "role": "assistant", "content": "For a two-person team...", "timestamp": "2026-07-08T14:03:14.000Z" } ] }
GET /sessions/{session_id} returns the record: session_id, chatbot_id, created_at, last_active_at, message_count, status. Attach your own metadata with PUT /sessions/{session_id}/context, close with POST /sessions/{session_id}/end (transcript retained), or erase entirely with DELETE /sessions/{session_id} for GDPR erasure requests.
curl -X PUT "https://spaces.sprinklenet.com/api/sessions/$SESSION_ID/context" \
-H "Content-Type: application/json" \
-H "X-API-Key: $KS_API_KEY" \
-d '{ "end_user_id": "user_42", "custom_context": { "plan": "enterprise" } }'
Sessions are created by the first chat call that uses the id; there's no separate "create session" step. Send at least one chat message with that session_id first, and check the id in the URL matches the one you chatted with exactly.
Subscribe to conversation and session events, verify each delivery's signature server-side, and respond so the platform marks it delivered.
Subscribable events: bot.conversation_started, bot.conversation_ended, session.created, session.ended, escalation.fired, plus wildcards like session.* and *. The create response includes the webhook's signing secret; store it next to your API key. On the current deployment, management calls under /orgs/{org_id}/webhooks may require a console session; if your key gets a 401 or 403, create the subscription in the console instead. Signing works identically either way.
curl -X POST "https://spaces.sprinklenet.com/api/orgs/$ORG_ID/webhooks" \
-H "Content-Type: application/json" \
-H "X-API-Key: $KS_API_KEY" \
-d '{
"name": "Ops alerting",
"url": "https://example.com/hooks/knowledge-spaces",
"events": ["session.ended", "escalation.fired"]
}'
Each delivery is an HTTP POST to your URL. The signature is HMAC-SHA256, hex-encoded, computed over the string "{timestamp}.{raw body}" with your webhook secret as the key. The timestamp is Unix epoch milliseconds.
POST /hooks/knowledge-spaces Content-Type: application/json X-Webhook-Signature: t=1751980393000,v1=5f2d8c...e9a1 X-Webhook-Timestamp: 1751980393000 X-Webhook-Event: session.ended { "event_type": "session.ended", "timestamp": "2026-07-08T14:33:13.000Z", "data": { ... } }
Verify against the raw bytes before parsing, reject anything older than 5 minutes, and compare in constant time.
import express from "express"; import crypto from "node:crypto"; const app = express(); // The signature covers the raw bytes, so use express.raw on this // route and verify BEFORE any JSON parsing. app.post( "/hooks/knowledge-spaces", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("X-Webhook-Signature") || ""; const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const t = Number(parts.t); const theirs = parts.v1 || ""; // Timestamp is Unix milliseconds. Reject anything outside 5 minutes. if (!t || Math.abs(Date.now() - t) > 5 * 60 * 1000) { return res.status(400).send("stale timestamp"); } const ours = crypto .createHmac("sha256", process.env.KS_WEBHOOK_SECRET) .update(`${t}.${req.body}`) // req.body is a Buffer here .digest("hex"); let valid = false; try { valid = crypto.timingSafeEqual(Buffer.from(ours), Buffer.from(theirs)); } catch { valid = false; } if (!valid) return res.status(401).send("bad signature"); const event = JSON.parse(req.body); console.log(req.get("X-Webhook-Event"), event.data); // Respond 2xx fast. Non-2xx and timeouts are retried with // exponential backoff (retry_count default 3, timeout_ms default 10s). res.sendStatus(200); } ); app.listen(3000);
POST /orgs/{org_id}/webhooks/{webhook_id}/test delivers a sample payload end to end. GET /orgs/{org_id}/webhooks/{webhook_id}/deliveries lists recent attempts with status, http_status, error_message, and next_retry_at, so you can see exactly why a delivery failed and when it retries.
Almost always a raw-body problem: a global express.json() ran first and you verified a re-serialized body whose bytes differ from what was signed. Mount express.raw() on the webhook route and compute the HMAC over that Buffer. Second most common: treating t as seconds. It's Unix epoch milliseconds; compare it to Date.now() directly.
BYOK is available today. Customer storage and private model endpoints are Enterprise architecture options, not free-tier toggles.
Enterprise deployments can evaluate customer-controlled object storage, region selection, encryption policy, and retention requirements.
For regulated teams, we can scope OpenAI-compatible private endpoints, customer cloud models, and egress controls as part of Enterprise setup.
Storage and private-model controls affect support, billing, security, and incident response, so they are configured through a managed Enterprise workflow.
Start on your own model key. Move up as you need managed models, team controls, and enterprise governance. Each tier includes everything below it.
An AI Answer is one grounded reply to your user. Failed or ungrounded replies are never billed. Managed-model usage requires a positive prepaid balance after the included pool is used.
Create your workspace, generate your Organization API Key, and make your first call today.
Start Building →