Skip to main content

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/v1

All 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.

ModelContextUse case
hermes32KFast chat, general queries, drafting
minos128KDeep reasoning, math, analysis
pluto64KCode generation, review, debugging
styx1MLong-document research, summarization
charon128KVision, OCR, document understanding

Chat endpoint

POST/api/v1/chat

Send 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

POST/api/v1/chat/stream

Returns 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

GET/api/v1/usage

Returns 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).

TierRPMTPMTokens / day
Free2040K250K
Pro500500K10M
Team2,0002MUnlimited

Error codes

Every non-2xx response has the same JSON shape:

{
  "error": {
    "code": "rate_limit",
    "message": "You exceeded 500 requests per minute."
  }
}
StatusCodeDescription
400bad_requestMalformed request body or missing required fields.
401unauthorizedMissing or invalid API key.
403forbiddenAPI key lacks access to the requested model or resource.
429rate_limitYou exceeded the requests-per-minute or token limits.
500server_errorAn unexpected error on our side — retry with backoff.
503overloadedUpstream 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.