# CentroSky API Reference Base URL: `https://centrosky.com/v1` CentroSky is OpenAI-compatible. If you have code using the OpenAI SDK, change the base URL and the API key and it works — including streaming. What CentroSky adds on top: **multi-agent orchestration**. A team of specialist agents can research, draft, review, and synthesize an answer together, and you can watch each agent's progress. --- ## Contents 1. [Authentication](#authentication) 2. [Quickstart](#quickstart) 3. [Models](#models) 4. [Chat Completions](#chat-completions) 5. [Streaming](#streaming) 6. [Agent Orchestration (sync)](#agent-orchestration-sync) 7. [Best-of-N Candidates](#best-of-n-candidates) 8. [Structured code output](#structured-code-output) 9. [Async Tasks](#async-tasks) 10. [Webhooks](#webhooks) 11. [Idempotency](#idempotency) 12. [Rate Limits](#rate-limits) 13. [Errors](#errors) 14. [SkyPower & Billing](#skypower-billing) --- ## Authentication Every request needs a bearer token: ```http Authorization: Bearer csky_your_key_here ``` Create keys at [centrosky.com](https://centrosky.com) → API. Keys are shown once at creation. `X-CentroSky-Key: csky_...` is accepted as an alternative header. Never ship a key in client-side code — it carries your SkyPower balance. --- ## Quickstart **cURL** ```bash curl https://centrosky.com/v1/chat/completions \ -H "Authorization: Bearer csky_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "model": "skymind-2", "messages": [{"role": "user", "content": "Explain database indexes briefly"}] }' ``` **Python (OpenAI SDK)** ```python from openai import OpenAI client = OpenAI( api_key="csky_your_key_here", base_url="https://centrosky.com/v1", ) resp = client.chat.completions.create( model="skymind-2", messages=[{"role": "user", "content": "Explain database indexes briefly"}], ) print(resp.choices[0].message.content) ``` **Node (OpenAI SDK)** ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: "csky_your_key_here", baseURL: "https://centrosky.com/v1", }); const resp = await client.chat.completions.create({ model: "skymind-2", messages: [{ role: "user", content: "Explain database indexes briefly" }], }); console.log(resp.choices[0].message.content); ``` --- ## Models `GET /v1/models` | Model | Description | |---|---| | `skymind-2` | **Default.** Flagship. Health-aware routing across the strongest live backends. Add `agents` for orchestration. | | `flash` | Fastest and cheapest. Good for classification, short replies, high volume. | | `sp` | Balanced mid-tier. | | `sp+` | Legacy premium alias. | `skymind-2` resolves to a concrete backend at request time based on provider health and prompt shape. The backend actually used is always reported in `centrosky.model_actual`, so routing is never a black box. **Routing hints** ```json { "model": "skymind-2", "reasoning": true, "messages": [...] } ``` `reasoning: true` routes to models stronger at multi-step problems. `task: "code"` routes to the dedicated code pool, led by Codestral with the strongest code-writing general backends behind it. Long prompts automatically route to a long-context pool. The pool used is reported in `centrosky.routing_pool` (`general`, `reasoning`, `code`, `long-context`). --- ## Chat Completions `POST /v1/chat/completions` ### Request | Field | Type | Default | Notes | |---|---|---|---| | `model` | string | `skymind-2` | See [Models](#models) | | `messages` | array | required | Standard OpenAI format | | `max_tokens` | int | 8192 | Total output budget, up to 131072. Budgets above a single backend's ceiling are automatically continued across backends until the answer is genuinely complete — see `centrosky.segments`. | | `max_segments` | int | 8 | Max continuation segments (1–12). | | `temperature` | float | 0.7 | | | `stream` | bool | false | See [Streaming](#streaming) | | `agents` | int \| `"auto"` | — | Enables orchestration. 1–20. | | `reasoning` | bool | false | Routing hint | | `task` | string | — | `"code"` routes `skymind-2` to the dedicated code pool (Codestral-led). | | `model_prefer` | array | — | Backend id substrings to try first, e.g. `["deepseek"]`. `skymind-2` only. | | `model_exclude` | array | — | Backend id substrings to never route to. A hard constraint — lets you act on `model_actual` telemetry. `skymind-2` only. | | `candidates` | int | — | 2–5. Best-of-N: N generations on N *different* backends; a reasoning-pool judge picks the winner. Non-streaming `skymind-2` only. See [Best-of-N](#best-of-n-candidates). | | `response_format` | string | — | `"code_files"` additionally parses fenced code blocks into structured `centrosky.code_files`. See [Structured code output](#structured-code-output). | ### Response ```json { "id": "csky-3f9a...", "object": "chat.completion", "created": 1753243200, "model": "skymind-2", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "An index is..." }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 14, "completion_tokens": 88, "total_tokens": 102 }, "centrosky": { "model_tier": "skymind-2", "model_actual": "openai/gpt-oss-120b", "provider": "groq", "routing_pool": "general", "spu_consumed": 0.102, "latency_ms": 843, "run_id": "3f9a..." } } ``` The `centrosky` block is an extension. OpenAI SDKs ignore unknown fields, so it never breaks compatibility. --- ## Streaming Set `stream: true` for token-by-token Server-Sent Events in OpenAI's `chat.completion.chunk` format. ```python stream = client.chat.completions.create( model="skymind-2", messages=[{"role": "user", "content": "Write a haiku about databases"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta if delta.content: print(delta.content, end="", flush=True) ``` Raw wire format: ``` data: {"id":"csky-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} data: {"id":"csky-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Silent"},"finish_reason":null}]} data: {"id":"csky-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{...},"centrosky":{...}} data: [DONE] ``` Notes: - The **final chunk carries `usage` and `centrosky`** — you get token counts and SP spend without a second call. - Errors that occur *before* streaming starts return a normal HTTP status (402, 429, 503). Errors *after* the first byte arrive in-band as `{"error": {...}}` followed by `[DONE]`, because HTTP status can no longer be changed once headers are sent. - If a provider fails before any content is emitted, CentroSky silently fails over to another. Once content has been sent it will **not** splice in a different model mid-answer — the stream ends with `finish_reason: "length"` instead. --- ## Agent Orchestration (sync) Add `agents` to any completion request: ```bash curl https://centrosky.com/v1/chat/completions \ -H "Authorization: Bearer csky_your_key_here" \ -d '{ "model": "skymind-2", "agents": 5, "messages": [{"role":"user","content":"Design a rate limiter for a REST API"}] }' ``` `"agents": "auto"` lets the router choose the team size. **This is synchronous and can take minutes.** For anything beyond a quick run, use [Async Tasks](#async-tasks) instead — long-held HTTP connections fail on most proxies, mobile networks, and serverless platforms. Response adds: ```json "centrosky": { "mode": "agents", "agent_count": 5, "agents_used": ["architect", "coder", "reviewer", "..."], "task_id": "9c2e...", "spu_consumed": 4.21 } ``` --- ## Best-of-N Candidates Add `candidates` (2–5) to a non-streaming `skymind-2` completion and the gateway runs the request on that many **different** backends in one shot, then has a reasoning-pool judge pick the best answer: ```bash curl https://centrosky.com/v1/chat/completions \ -H "Authorization: Bearer csky_your_key_here" \ -d '{ "model": "skymind-2", "task": "code", "candidates": 3, "max_tokens": 16000, "messages": [{"role":"user","content":"Write a PHP rate limiter class with sliding window"}] }' ``` The winning answer comes back as a normal completion. The tournament is reported under `centrosky.candidates`: ```json "centrosky": { "mode": "candidates", "candidates": { "requested": 3, "completed": 3, "winner": 2, "judge_model": "deepseek-reasoner", "judge_reason": "Candidate 2 is the only complete implementation with correct window carry-over.", "entries": [ { "candidate": 1, "model": "codestral-latest", "tokens_out": 1420, "truncated": false, "winner": false }, { "candidate": 2, "model": "openai/gpt-oss-120b", "tokens_out": 1610, "truncated": false, "winner": true }, { "candidate": 3, "model": "deepseek-chat", "tokens_out": 1388, "truncated": false, "winner": false } ] } } ``` Notes: - Backend diversity is forced: each candidate excludes every model already used, so you are genuinely comparing different models, not the same one three times. - **You are billed for all N generations plus the judge** — `usage` and `spu_consumed` cover the whole tournament. This is a quality dial, priced accordingly. - `model_exclude` applies to candidates and the judge alike. `model_prefer` orders which backends get picked first. - If some backends fail, the tournament runs with the survivors. If only one survives, it wins by default. If none survive, the request falls back to a normal single completion. - Watch which models win your workloads over time (`entries[].winner`) and steer with `model_prefer` — the tournament generates its own routing data. --- ## Structured code output Add `response_format: "code_files"` to any completion and the gateway parses fenced code blocks out of the answer server-side: ```json "centrosky": { "code_files_count": 2, "code_files": [ { "path": "includes/RateLimiter.php", "language": "php", "content": " bool: parts = dict(p.split("=", 1) for p in header.split(",")) ts, sig = parts["t"], parts["v1"] if abs(time.time() - int(ts)) > tolerance: return False # stale — replay attempt expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sig) ``` ```php function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool { parse_str(str_replace(',', '&', $header), $p); if (abs(time() - (int)$p['t']) > $tolerance) return false; $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret); return hash_equals($expected, $p['v1']); } ``` Delivery: retried up to 5 times, then marked failed. Respond `2xx` to acknowledge. Redirects are not followed. Treat webhooks as at-least-once and make your handler idempotent. --- ## Idempotency Send `Idempotency-Key` on any POST to make retries safe: ```http Idempotency-Key: 8f14e45f-ea1c-4d2b-9f0e-1a2b3c4d5e6f ``` Use a fresh UUID per logical operation. | Situation | Result | |---|---| | First request | Executes normally, response cached | | Replay after completion | Cached response + `Idempotency-Replayed: true` | | Replay while still running | `409` | | Same key, different body | `422` | | Original failed | Not cached — retry executes normally | Keys are scoped to your account and expire after 24 hours. Not supported on streaming requests, which cannot be meaningfully replayed. This matters most for agent runs: without a key, a client timeout plus an automatic SDK retry means the work runs twice and you are billed twice. --- ## Rate Limits Per-minute request limits by plan. Every response includes: ```http X-RateLimit-Limit: 60 X-RateLimit-Remaining: 57 X-RateLimit-Reset: 1753243260 ``` `X-RateLimit-Reset` is a Unix timestamp. On `429` you also get `Retry-After` in seconds. **Agent runs cost more than one unit** — an `agents: 5` request consumes 5 units, because it does roughly five times the upstream work. Polling task status costs nothing. **Partner keys.** First-party integrations can be exempted from per-minute limits entirely by setting `rate_limit_rpm = -1` on the API key row (`centrosky_api_keys`). SP metering still applies — this removes the request throttle, not the billing. Recommended for pipelines that legitimately multiply request volume (best-of-N candidates, multi-pass review chains). Handle 429 with exponential backoff: ```python import time, requests def call(payload, key, attempts=5): for i in range(attempts): r = requests.post("https://centrosky.com/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json=payload) if r.status_code != 429: return r time.sleep(int(r.headers.get("Retry-After", 2 ** i))) raise RuntimeError("rate limited after retries") ``` --- ## Errors Standard OpenAI-compatible envelope: ```json { "error": { "message": "Rate limit exceeded: 60 requests/min on your plan.", "type": "rate_limit_error", "code": "rate_limit_exceeded", "retry_after": 12 } } ``` | HTTP | `type` | Meaning | Action | |---|---|---|---| | 400 | `invalid_request` | Malformed body | Fix the request | | 401 | `authentication_error` | Missing/invalid key | Check the key | | 402 | `insufficient_quota` | Out of SkyPower | [Top up](https://centrosky.com/#/pricing) | | 404 | `not_found` | Unknown task id | Check the id | | 409 | `invalid_request` | Idempotency key in flight | Wait, then poll | | 422 | `invalid_request` | Idempotency key reused with new body | Use a fresh key | | 429 | `rate_limit_error` | Too many requests | Back off per `Retry-After` | | 500 | `server_error` | Internal failure | Retry with backoff | | 502 | `server_error` | Upstream provider failed | Retry | | 503 | `server_error` | No provider available | Retry shortly | `402` and `422` are **not** retryable without changing something. Everything else is safe to retry with backoff. --- ## SkyPower & Billing Usage is metered in **SkyPower (SP)**, paid in **CENTRO (₵)**. Every response reports consumption: ```json "centrosky": { "spu_consumed": 0.102 } ``` Also exposed as headers on non-streaming completions: ```http X-SkyPower-Consumed: 0.102 X-Request-Id: 3f9a... ``` SP consumption scales with tokens and a per-model weight — `flash` costs less per token than `skymind-2`. Agent runs consume roughly in proportion to team size. Plans, pack pricing, and current balance: [centrosky.com/#/pricing](https://centrosky.com/#/pricing) When SP runs out you get `402` with `code: "sp_exhausted"`. --- ## Support - Dashboard: [centrosky.com](https://centrosky.com) - Pricing: [centrosky.com/#/pricing](https://centrosky.com/#/pricing) Include `centrosky.run_id` (or `X-Request-Id`) when reporting an issue — it identifies the exact run. --- ## Network SkyPower telemetry ### `GET /v1/network/skypower` Public aggregate telemetry for explorers and network-status displays. No authentication is required because the response contains no keys, prompts, wallets or user records. Key sections: - `capacity`: theoretical, sustained, effective, burst and available SP - `demand`: current, 5-minute and 1-hour SP; 24-hour peak and completed SPU - `utilization`: compute, concurrency, quota and request pressure - `providers`: aggregate health, slot counts and provider-type contribution - `operations`: queue depth, success rate and latency percentiles - `measurement`: coverage and formulas - `history`: five-minute capacity and demand rollups for the last 24 hours `1 SP = 1 SPU/second`.