SDK and examples
Everything you need to call Shara from your code: Bearer token authentication, the endpoint an API key can use and ready-to-copy examples in curl, TypeScript (fetch) and Python. The API is plain REST over HTTPS, with JSON bodies and responses, so any standard HTTP client works. There is no public SDK yet: the official SDKs arrive with general availability (GA) and the internal package is private.
Base URL and format
Section titled “Base URL and format”The host is https://api.sharasaas.com. Transport is HTTPS only (TLS 1.2+), request and response bodies are JSON (Content-Type: application/json) and streaming mode returns Server-Sent Events (text/event-stream). Inference and the database are hosted in the European Union.
| Property | Value |
|---|---|
| Endpoint | POST https://api.sharasaas.com/v1/inference |
| Protocol | HTTPS (TLS 1.2+) |
| Format | application/json · streaming in text/event-stream |
| Authentication | Authorization: Bearer <api-key> |
| Rate limit | 60 req/min per IP and route · 600 req/min per workspace and route (X-RateLimit-* headers) |
| Maximum body size | 512 KB |
| Traceability | X-Request-Id header on every response |
Bearer token authentication
Section titled “Bearer token authentication”Integrations authenticate with a Shara API key. You generate it from Settings → Security and send it on every request with the standard Authorization header:
Authorization: Bearer shara_sk_live_YOUR_API_KEY
The key has the fixed prefix shara_sk_live_ followed by a secret part. It is shown in full only once, at the moment you create it: save it in your secrets manager. After that, the dashboard only shows the prefix so you can identify it. Each key carries scopes (minimum permissions; to invoke agents it needs amadeus:invoke, or amadeus:invoke:agent:<slug> for a single agent) and expires after 90 days by default (365 at most).
Rotating a key revokes the old one first and then creates the new one, so there is a cut. To avoid it: create the new key, deploy it and only then revoke the old one. Key management routes, scopes and expiry are in the REST API page.
Treat the API key like a server password: never embed it in the browser, in mobile apps or in public repositories. It lives in backend environment variables. If you suspect it has leaked, revoke it from
Settings → Securityand issue a new one.
Main endpoints
Section titled “Main endpoints”An API key works on POST /v1/inference (and on the MCP bridge, with the mcp:tenant scope). The workspace orchestrator is Amadeus: it receives your request, decides which departmental agent handles it and coordinates the work. You can address Amadeus (and let it route) or a specific agent by its slug in the agentSlug field.
| Method | Path | What it does |
|---|---|---|
POST |
/v1/inference |
Sends the turn to an agent and returns the result in the same response (normal or SSE). |
Routes such as /runs and /usage/current only work with a signed-in app session, not with an API key. Shara does not offer inbound webhooks yet. The full list of routes is in the REST API page.
Every agent has an API slug, and the API always uses it, whatever name pack your company has chosen. The full table (public name, department and slug) is in the agent catalog. The slug is the agent’s public name in lowercase (amadeus, wanamaker, patterson, graham, holmes, kerckhoffs…).
The agents available to your company depend on your plan and on the agents you chose or hired. An agent that is not available to you answers 403.
Create an inference (curl)
Section titled “Create an inference (curl)”A minimal request sends a model alias and the list of messages. The alias is the only thing accepted to select the model: Prelude (agile and economical, on every plan), Sonata (cost/quality balance, from Pro) and Symphony (the most capable, from Max) are the cloud models, in growing order of capacity; Concerto is the local model of the Concerto Local plan. Your plan sets the ceiling: asking for an alias above it returns 422 alias_plan_mismatch.
curl -sS https://api.sharasaas.com/v1/inference \
-H "Authorization: Bearer shara_sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"alias": "Sonata",
"agentSlug": "amadeus",
"messages": [
{ "role": "user", "content": "Summarize this week’s support news." }
],
"effort": "medium"
}'
The call is synchronous: the response (200) carries the alias used, the content (already redacted), the usage breakdown in tokens and how the turn ended in status (completed, failed, blocked or canceled; read it, because a failed or blocked turn is still HTTP 200):
{
"alias": "Sonata",
"content": "This week support closed 34 tickets…",
"usage": {
"inputTokens": 184,
"outputTokens": 412
},
"status": "completed",
"runId": "3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64"
}
Depending on the turn, it can also carry toolOutcome, blockedReason and motivoDeCorte. All fields are described in REST API.
Streaming (SSE)
Section titled “Streaming (SSE)”With "stream": true the response arrives as text/event-stream: fragments as data: {"delta":"…"} and a closing end event with status, runId and the token counts. Use curl -N to see it live:
curl -sS -N https://api.sharasaas.com/v1/inference \
-H "Authorization: Bearer shara_sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"alias": "Prelude",
"stream": true,
"messages": [{ "role": "user", "content": "Write a two-line greeting." }]
}'
data: {"delta":"Hello, "}
data: {"delta":"welcome."}
event: end
data: {"done":true,"status":"completed","runId":"3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64","alias":"Prelude","usage":{"inputTokens":21,"outputTokens":9}}
The stream may carry other named events (for example usage or error); ignore the ones you don’t use.
The same example with fetch (TypeScript)
Section titled “The same example with fetch (TypeScript)”Equivalent in Node.js / TypeScript with native fetch. Always branch on the error field (a stable string), never on the text of message:
const BASE = 'https://api.sharasaas.com';
const API_KEY = process.env.SHARA_API_KEY; // never on the client
if (API_KEY === undefined) throw new Error('Missing SHARA_API_KEY');
interface InferenceResponse {
alias: string;
content: string;
usage: { inputTokens: number; outputTokens: number };
status: 'completed' | 'failed' | 'blocked' | 'canceled';
runId?: string;
toolOutcome?: { rejected: string[]; expired: string[] };
blockedReason?: string;
motivoDeCorte?: 'tope_de_pasos' | 'tope_de_gasto' | 'sin_cierre';
}
const res = await fetch(`${BASE}/v1/inference`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
alias: 'Sonata',
agentSlug: 'amadeus',
messages: [
{ role: 'user', content: 'Summarize this week’s support news.' },
],
effort: 'medium',
}),
});
if (!res.ok) {
const { error } = (await res.json()) as { error: string };
throw new Error(`Shara API ${res.status}: ${error}`);
}
const data = (await res.json()) as InferenceResponse;
console.log(data.status, data.content, data.usage);
And reading the stream:
const stream = await fetch(`${BASE}/v1/inference`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
alias: 'Prelude',
stream: true,
messages: [{ role: 'user', content: 'Write a two-line greeting.' }],
}),
});
if (!stream.ok || stream.body === null) throw new Error(`Shara API ${stream.status}`);
const reader = stream.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const blocks = buffer.split('\n\n');
buffer = blocks.pop() ?? '';
for (const block of blocks) {
const event = /^event: (.+)$/m.exec(block)?.[1] ?? 'message';
const raw = /^data: (.+)$/m.exec(block)?.[1];
if (raw === undefined) continue;
let payload: { delta?: string; status?: string; runId?: string };
try {
payload = JSON.parse(raw);
} catch {
continue; // not a JSON event: ignore it
}
if (event === 'message' && payload.delta !== undefined) process.stdout.write(payload.delta);
if (event === 'end') console.log('\n', payload.status, payload.runId);
}
}
The same example in Python
Section titled “The same example in Python”import json
import os
import requests
BASE = "https://api.sharasaas.com"
HEADERS = {"Authorization": f"Bearer {os.environ['SHARA_API_KEY']}"} # server side only
# Normal mode: the result comes in this same response.
resp = requests.post(
f"{BASE}/v1/inference",
headers=HEADERS,
json={
"alias": "Sonata",
"agentSlug": "amadeus",
"messages": [{"role": "user", "content": "Summarize this week’s support news."}],
"effort": "medium",
},
)
if not resp.ok:
raise RuntimeError(f"Shara API {resp.status_code}: {resp.json().get('error')}")
data = resp.json()
print(data["status"], data["content"])
# Streaming mode (SSE).
with requests.post(
f"{BASE}/v1/inference",
headers=HEADERS,
json={
"alias": "Prelude",
"stream": True,
"messages": [{"role": "user", "content": "Write a two-line greeting."}],
},
stream=True,
) as stream:
stream.raise_for_status()
stream.encoding = "utf-8"
event = "message"
for line in stream.iter_lines(decode_unicode=True):
if not line:
event = "message"
continue
if line.startswith("event: "):
event = line[len("event: "):]
elif line.startswith("data: "):
try:
payload = json.loads(line[len("data: "):])
except json.JSONDecodeError:
continue # not a JSON event: ignore it
if event == "message" and "delta" in payload:
print(payload["delta"], end="", flush=True)
elif event == "end":
print("\n", payload.get("status"), payload.get("runId"))
Responses never reveal the real model behind an alias. A redactor processes every output (content, error messages and tool dumps) before it crosses the network, so you will only see
Prelude·Sonata·Symphony·Concerto. Don’t depend on provider names: they won’t appear.
Rate limits
Section titled “Rate limits”The limit uses a fixed 60-second window with a counter per route: 60 requests per IP and route and 600 requests per workspace and route. Every response carries headers so your client can regulate itself; use them instead of guessing:
X-RateLimit-Limit: request cap for the current window.X-RateLimit-Remaining: requests you have left; slow down before reaching 0.X-RateLimit-Reset: epoch (seconds) when the window resets.Retry-After: only on429, seconds to wait before retrying.
When you exceed the limit you get 429 with { "error": "rate_limit_exceeded", "retryAfterSec": <n> }; respect Retry-After with exponential backoff. Also keep in mind two cuts distinct from the request limit: quota_exceeded when your STU quota reaches the blocking level (200% of the quota), and kill_switch when a spending cap trips (by default 1M STU per run, 10M per hour and 80M per day; an owner or administrator can adjust or disable them). Both are also 429, but they are resolved by waiting for the quota reset or reviewing consumption, not by retrying in a loop.
Official SDKs
Section titled “Official SDKs”With general availability (GA) we will publish official SDKs for Node.js / TypeScript and Python that wrap authentication, retries and streaming. In the meantime, the examples on this page cover the endpoint an API key can use: it is plain REST, with standard headers and JSON, and works with any HTTP client.
Want early access to the API or to raise your request limit per workspace? Write to us at
api@aiginer.comwith your use case, or tosupport@aiginer.comfor any integration question.