Developer documentation
Build with sasuAI
One REST endpoint, one API key, pay-as-you-go by the token. Send a conversation, get an answer back — streaming or all at once.
Introduction
The sasuAI API gives your own application direct access to the same model that powers sasuschat's chatbots. It speaks plain JSON over HTTPS, so any language that can make an HTTP request can use it — no SDK required.
- Billing is prepaid credit, metered per token — no subscription, no minimum.
- Every new account starts with 0.50 GHS of free credit, enough to try it properly.
- Keys are shown once and stored only as a hash. Revoke any key instantly from your dashboard.
Using an AI coding agent? Point Claude, Cursor, or Copilot at https://chat.sasusync.com/docs/agent.md — a plain-text integration brief it can read directly and implement from, no copy-pasting required.
Authentication
Every request carries your secret key in an Authorization header.
Keys start with sk-sasu-.
Authorization: Bearer sk-sasu-your-key-here
Keep keys server-side. A key in browser or mobile code can be read by anyone who opens developer tools, and every call they make is billed to you. Put sasuAI calls behind your own backend endpoint.
Getting a key
- Create an account — or log in if you already have one.
- Open sasuAI API in the dashboard sidebar.
- Name a key and click Create key, then copy it — it is never shown again.
Quickstart
Your first call, in seven languages. Each one sends a single user message and prints the reply.
curl https://chat.sasusync.com/v1/chat/completions \ -H "Authorization: Bearer $SASUAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "sasuai-core", "messages": [ {"role": "user", "content": "Write a one-line welcome message."} ] }'
import os, requests r = requests.post( "https://chat.sasusync.com/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['SASUAI_API_KEY']}"}, json={ "model": "sasuai-core", "messages": [ {"role": "user", "content": "Write a one-line welcome message."} ], }, timeout=60, ) r.raise_for_status() print(r.json()["choices"][0]["message"]["content"])
// Node 18+ has fetch built in - no dependency needed. const res = await fetch("https://chat.sasusync.com/v1/chat/completions", { method: "POST", headers: { "Authorization": `Bearer ${process.env.SASUAI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "sasuai-core", messages: [ { role: "user", content: "Write a one-line welcome message." }, ], }), }); const data = await res.json(); console.log(data.choices[0].message.content);
<?php $ch = curl_init("https://chat.sasusync.com/v1/chat/completions"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("SASUAI_API_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "model" => "sasuai-core", "messages" => [ ["role" => "user", "content" => "Write a one-line welcome message."], ], ]), ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo $response["choices"][0]["message"]["content"];
import java.net.URI; import java.net.http.*; HttpClient client = HttpClient.newHttpClient(); String body = """ {"model":"sasuai-core","messages":[{"role":"user","content":"Write a one-line welcome message."}]}"""; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://chat.sasusync.com/v1/chat/completions")) .header("Authorization", "Bearer " + System.getenv("SASUAI_API_KEY")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); // response.body() is the raw JSON - parse it with whatever JSON library your project already uses. System.out.println(response.body());
package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) func main() { body, _ := json.Marshal(map[string]any{ "model": "sasuai-core", "messages": []map[string]string{ {"role": "user", "content": "Write a one-line welcome message."}, }, }) req, _ := http.NewRequest("POST", "https://chat.sasusync.com/v1/chat/completions", bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("SASUAI_API_KEY")) req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() var result map[string]any json.NewDecoder(resp.Body).Decode(&result) choices := result["choices"].([]any) message := choices[0].(map[string]any)["message"].(map[string]any) fmt.Println(message["content"]) }
// Never put the API key in browser code - anyone who opens devtools can read // and spend it. Call your own backend, which holds the key server-side (see // "Keep keys server-side" above), and have that backend call sasuAI. async function askSasuAI(message) { const res = await fetch("/api/ask", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message }), }); const data = await res.json(); return data.reply; } function Chat() { const [reply, setReply] = useState(""); return ( <button onClick={async () => setReply(await askSasuAI("Write a one-line welcome message."))}> Ask sasuAI </button> ); }
The response
{
"id": "chatcmpl-1757000000-a1b2c3d4",
"object": "chat.completion",
"created": 1757000000,
"model": "sasuai-core",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Welcome aboard — glad you're here!" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 14, "completion_tokens": 9, "total_tokens": 23 }
}
Chat completions
Generates a reply for a conversation. Send the whole conversation each time — the API is stateless and keeps no history between calls.
Request body
| Field | Type | Description |
|---|---|---|
| messages required | array | The conversation so far, oldest first. Up to 40 messages and 24,000 characters in total. |
| model | string | Which model to use. Defaults to sasuai-core. |
| stream | boolean | Set true to receive the answer token by token. Defaults to false. |
Message object
| Field | Type | Description |
|---|---|---|
| role required | string | One of system, user, or assistant. A leading system message sets the behaviour and tone for the whole conversation. |
| content required | string | The text of the message. |
A multi-turn conversation
{
"model": "sasuai-core",
"messages": [
{ "role": "system", "content": "You are a support agent for Acme Ltd. Be brief." },
{ "role": "user", "content": "Do you ship to Kumasi?" },
{ "role": "assistant", "content": "Yes, next-day delivery to Kumasi." },
{ "role": "user", "content": "How much does that cost?" }
]
}
Response fields
| Field | Description |
|---|---|
| id | Unique id for this completion. |
| model | The model that answered. |
| choices[0].message.content | The generated reply. |
| choices[0].finish_reason | stop when the model finished on its own. |
| usage | Token counts for the call — what you were billed on. |
Streaming
Set "stream": true and the reply arrives as server-sent events,
so you can render text as it is written instead of waiting for the whole answer.
Each event is a line beginning data:; the stream ends with
data: [DONE].
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"sasuai-core",
"choices":[{"index":0,"delta":{"content":"Welcome"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"sasuai-core",
"choices":[{"index":0,"delta":{"content":" aboard"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","model":"sasuai-core",
"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
import json, os, requests with requests.post( "https://chat.sasusync.com/v1/chat/completions", headers={"Authorization": f"Bearer {os.environ['SASUAI_API_KEY']}"}, json={ "model": "sasuai-core", "messages": [{"role": "user", "content": "Tell me a short story."}], "stream": True, }, stream=True, timeout=120, ) as r: r.raise_for_status() for line in r.iter_lines(decode_unicode=True): if not line or not line.startswith("data: "): continue payload = line[6:] if payload == "[DONE]": break delta = json.loads(payload)["choices"][0]["delta"] print(delta.get("content", ""), end="", flush=True)
const res = await fetch("https://chat.sasusync.com/v1/chat/completions", { method: "POST", headers: { "Authorization": `Bearer ${process.env.SASUAI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "sasuai-core", messages: [{ role: "user", content: "Tell me a short story." }], stream: true, }), }); const reader = res.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += value; // Events are separated by a blank line; keep any partial tail in the buffer. const events = buffer.split("\n\n"); buffer = events.pop(); for (const event of events) { if (!event.startsWith("data: ")) continue; const payload = event.slice(6); if (payload === "[DONE]") break; const text = JSON.parse(payload).choices[0].delta.content ?? ""; process.stdout.write(text); } }
Streamed calls are billed for what was produced. If you disconnect halfway through, you pay for the tokens generated up to that point, not the whole reply.
Models
Lists the models available to your key, with live pricing.
| Model | Best for | Context |
|---|---|---|
| sasuai-core | Support chat, summarising, drafting, classification and extraction. | 24,000 chars per request |
curl https://chat.sasusync.com/v1/models \
-H "Authorization: Bearer $SASUAI_API_KEY"
Errors
Failures return a JSON body with an error object and a matching HTTP status.
{
"error": {
"message": "Invalid or revoked API key.",
"type": "authentication_error",
"code": null
}
}
| Status | Type | What it means |
|---|---|---|
| 400 | invalid_request_error | Something in the request body is missing or malformed — check messages. |
| 401 | authentication_error | The key is missing, mistyped, or revoked. |
| 402 | insufficient_quota | Your credit balance is empty. Top up to resume. |
| 429 | rate_limit_error | Too many requests too quickly. Back off and retry. |
| 502 | server_error | The model could not complete the request. Safe to retry. |
Pricing & credits
You are billed per token — roughly four characters of text — with input priced lower than output. Credits never expire, and calls stop cleanly at a zero balance rather than running up a bill.
Checking what a call cost
Every response carries a usage block with the exact token counts
used for billing. Your dashboard's sasuAI API page shows the same figures per
call, alongside your balance and full credit history.
Topping up
Add credit from the dashboard in a few taps — payment is by mobile money, and the balance is available the moment payment confirms.
Rate limits
Limits are per key and generous enough for normal production traffic.
| Endpoint | Limit |
|---|---|
| POST /v1/chat/completions | 120 requests per minute · 5,000 per day |
| GET /v1/models | 60 requests per minute |
Exceeding a limit returns 429. Retry with exponential backoff —
wait a second, then two, then four — rather than retrying immediately in a loop.
Best practices
- Keep the key on your server. Never ship it in browser, mobile, or desktop code.
- Use one key per environment. Separate keys for staging and production mean you can revoke one without downtime for the other.
- Trim old turns. You pay for every message you resend, so drop the parts of a long conversation the model no longer needs.
- Put instructions in a system message. Tone, format and rules belong there, not repeated in every user message.
- Set a timeout and retry on 429 and 502. Both are transient; 400 and 401 are not — fix those instead of retrying.
- Stream anything long. Users see the first words in under a second, which reads as far faster than the same answer delivered whole.
Questions?
Email [email protected] — we answer developer questions directly.
Calling your chatbot directly
This is a different, second API from everything above — every chatbot you create already
has its own conversational endpoint, the same one the embed widget calls. No sasuAI key,
no per-token billing: it's scoped by the chatbot's own public_id
(shown on its card in the dashboard) and metered against that chatbot's plan limits instead.
Reach for this when you're building your own chat UI — a native app, a Slack bot, a support
console — rather than dropping in the widget script tag.
curl https://chat.sasusync.com/api/chat/YOUR_CHATBOT_PUBLIC_ID \ -H "Content-Type: application/json" \ -d '{ "message": "Do you ship internationally?", "history": [] }'
The response is plain streamed text, not JSON — read the body as it arrives and render it as-is; there's no envelope to parse.
Request body
| Field | Type | Description |
|---|---|---|
| message | string | The visitor's message. Optional if image is present. |
| history | array | Prior turns, oldest first: [{"role": "user"|"assistant", "content": "..."}]. Stateless — resend it every call. |
| image | string | Optional. A base64 data URI (data:image/png;base64,...) — never a link. Routes this turn to the vision model automatically. Max ~4MB. |
Leads
If the chatbot has lead capture on, POST the same shape to
/api/chat/<public_id>/lead with
{"name", "email", "phone", "note"} (at least one of the first
three) — it returns JSON, not a stream.
CORS is open on this endpoint for the widget's sake, so it's safe to call
from your own frontend too — no key to leak. Calling it from your own backend works the
same way, just without an Origin header to worry about.
If you've restricted the chatbot's allowed origins in its settings, calls from a browser
on a different origin are rejected; server-to-server calls are unaffected.
Errors & limits
404 unknown or disabled chatbot · 400
bad request body · 403 origin not allowed ·
429 this chatbot or your account has hit its message limit for
now — chatbots have their own daily ceiling independent of the account's monthly one, so a
burst against one bot can't spend a whole month's quota at once.