Developers
API documentation
A REST API for the same five models that power the SpyCore chat app. Authenticate with an API key, send chat messages, stream tokens over SSE, and track per-model usage.
Introduction
The SpyCore API lets you send messages to any of our five models and receive responses as JSON or as an SSE stream. All endpoints live under a single base URL.
Base URL:
https://api.spycore.ca/v1All requests use application/json for request bodies and responses, and must include an Authorization header with a bearer token.
Authentication
API keys
Authenticate every request with a bearer token in theAuthorizationheader. Get your key from Settings → API. Keys start with sk-. Treat them like passwords and never commit them to source control.
Example request
curl https://api.spycore.ca/v1/chat \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"model":"hermes","messages":[{"role":"user","content":"Ping"}]}'Available models
Pass the model name as themodelfield in any chat request.
| Model | Context | Use case |
|---|---|---|
| hermes | 32K | Fast chat, general queries, drafting |
| minos | 128K | Deep reasoning, math, analysis |
| pluto | 64K | Code generation, review, debugging |
| styx | 1M | Long-document research, summarization |
| charon | 128K | Vision, OCR, document understanding |
Chat endpoint
/api/v1/chatSend a list of chat messages and receive a single complete response. For token-by-token delivery, use the streaming endpoint instead.
Request body
{
"model": "hermes",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Hello, SpyCore." }
],
"temperature": 0.7,
"max_tokens": 1024
}Response schema
{
"id": "chat_01HXYZ...",
"model": "hermes",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hi there — how can I help?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 12,
"total_tokens": 40
}
}curl example
curl https://api.spycore.ca/v1/chat \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{
"model": "hermes",
"messages": [{"role": "user", "content": "Hello"}],
"temperature": 0.7,
"max_tokens": 1024
}'JavaScript example
const res = await fetch("https://api.spycore.ca/v1/chat", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SPYCORE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "hermes",
messages: [{ role: "user", content: "Hello" }],
temperature: 0.7,
max_tokens: 1024,
}),
});
const data = await res.json();
console.log(data.choices[0].message.content);Streaming
/api/v1/chat/streamReturns a text/event-stream of server-sent events. Each event is a data: {...}\n\n chunk containing an incremental token. The stream ends with a final data: [DONE] sentinel.
curl example
curl https://api.spycore.ca/v1/chat/stream \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-N \
-d '{
"model": "pluto",
"messages": [{"role": "user", "content": "Write a quicksort in TypeScript"}]
}'JavaScript example
const res = await fetch("https://api.spycore.ca/v1/chat/stream", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SPYCORE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "hermes",
messages: [{ role: "user", content: "Stream me a haiku" }],
}),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (const line of buffer.split("\n\n")) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6).trim();
if (payload === "[DONE]") return;
const chunk = JSON.parse(payload);
process.stdout.write(chunk.delta ?? "");
}
buffer = buffer.split("\n\n").pop() ?? "";
}Usage endpoint
/api/v1/usageReturns your token usage for the current billing month, broken down by model.
{
"period": "2026-04",
"total_tokens": 1248203,
"by_model": {
"hermes": { "prompt": 412_000, "completion": 98_000 },
"minos": { "prompt": 180_200, "completion": 42_300 },
"pluto": { "prompt": 220_500, "completion": 72_100 },
"styx": { "prompt": 95_400, "completion": 18_700 },
"charon": { "prompt": 84_003, "completion": 25_000 }
}
}Rate limits
Limits apply per API key and reset at the top of each minute (RPM / TPM) and each UTC day (TPD).
| Tier | RPM | TPM | Tokens / day |
|---|---|---|---|
| Free | 20 | 40K | 250K |
| Pro | 500 | 500K | 10M |
| Team | 2,000 | 2M | Unlimited |
Error codes
Every non-2xx response has the same JSON shape:
{
"error": {
"code": "rate_limit",
"message": "You exceeded 500 requests per minute."
}
}| Status | Code | Description |
|---|---|---|
| 400 | bad_request | Malformed request body or missing required fields. |
| 401 | unauthorized | Missing or invalid API key. |
| 403 | forbidden | API key lacks access to the requested model or resource. |
| 429 | rate_limit | You exceeded the requests-per-minute or token limits. |
| 500 | server_error | An unexpected error on our side — retry with backoff. |
| 503 | overloaded | Upstream capacity is saturated. Retry after a short wait. |
Ready to build?
Grab an API key from your settings and start sending requests in under a minute.