Skip to content

REST API

Shara exposes a REST API over HTTPS for programmatic integrations. The host is https://api.sharasaas.com, responses are JSON and service-level authentication uses an API key with scopes. It’s in private beta and opens up at general availability (GA). This page is the technical reference for authentication, endpoints, errors, rate limits and SDKs.

The API is REST over HTTPS. The host is https://api.sharasaas.com. Not every endpoint hangs off a uniform /v1 prefix: agent invocation and the MCP bridge do (/v1/inference, /v1/mcp/…), while key management, runs and usage live at the root (/tenants/me/api-keys, /me/api-keys, /runs, /usage/current) This page always uses each endpoint’s real, full route. Request and response bodies are JSON (Content-Type: application/json), except in streaming mode, which returns Server-Sent Events (text/event-stream).

An API key only works on two surfaces: POST /v1/inference and the MCP bridge. Every other route on this page (key management, runs, usage) needs the session of a signed-in member of the app; it does not accept an API key.

Three principles run through the whole public surface: (1) models are always referenced by a musical alias (Prelude, Sonata, Symphony, Concerto) and never by their provider; (2) every response passes through a redactor before crossing the network, so neither error messages nor tool dumps leak internal details; (3) the identity of the caller (tenant, user, agent) is always derived from the credential, never from the body of the request.

Property Value
Host https://api.sharasaas.com
Protocol HTTPS required (TLS 1.2+)
Format application/json · streaming over text/event-stream
Service authentication API key via Authorization: Bearer <api-key> (only POST /v1/inference and the MCP bridge)
Rate limit Fixed 60 s window · 60 req per IP and route · 600 req per workspace and route (see X-RateLimit-* headers)
Maximum body size 512 KB (413 request_too_large above that)
Traceability X-Request-Id header on every response

Versioning: the current version is v1. When we publish breaking changes we’ll do it under a new prefix (/v2) and keep /v1 running through the announced deprecation period.

Server integrations authenticate with a Shara API key. You generate it at Settings → Security (requires an administrator role for company keys; if your account has two-step verification, it must be completed in that session) and it’s sent on every request via the standard header:

Authorization: Bearer shara_sk_live_YOUR_API_KEY

Key format: fixed prefix shara_sk_live_ followed by 24 base62 characters (38 characters in total). The full value is shown only once, at creation time: save it in your secrets manager. Afterwards, the panel only shows the visible prefix (shara_sk_live_ + 6 characters) so you can identify it without being able to recover the secret.

Verification is robust by design: the key is looked up by prefix (only active, non-expired and non-revoked keys are considered) and the secret is compared against its hash using a robust hashing function and constant-time comparison. A short cache avoids burst rehashing without weakening revocation.

There are two kinds of key. Company keys are a service principal of the workspace and are limited by their scopes. Personal keys act as the person who created them, with that person’s role and department, and never have more permissions than that person.

Each API key carries one or more scopes that bound what it can do. Always grant the minimum necessary (principle of least privilege). These are the scopes a route checks today:

Scope Allows
amadeus:invoke Invoke any agent via POST /v1/inference, the single invocation endpoint (routes to the Amadeus orchestrator or a specific agent depending on agentSlug).
amadeus:invoke:agent:<slug> Granular variant: restricts the key to a single agent by its slug. You can add several.
mcp:tenant Access to the per-tenant MCP bridge (tools via SSE/JSON-RPC). It must appear literally in the key; wildcards don’t grant it.
workspace:all Shortcut available only on company keys: it expands to the whole company scope catalog.

The key creator may list other scope names from its catalog, but no API-key route requires them today, so they grant nothing. The wildcard resource:* expands to that resource’s scopes in the catalog; amadeus:* expands to amadeus:invoke (it does not add the per-agent scopes). The total wildcard *:* is not accepted when creating a key. If a key can’t invoke at all you get 403 scope_required; if it can invoke but not the requested agent, 403 agent_scope_required (with agentSlug and a message naming the missing scope). Company keys that invoke agents (amadeus:*, workspace:all) can only be created or rotated by owner, ceo or admin (ceo is a legacy alias of owner); otherwise you get 403 scope_forbidden.

Manage keys from these endpoints. They need a signed-in session, not an API key.

Method Route What it does
GET /tenants/me/api-keys Lists the company keys (no secret or hash). Roles owner, ceo, admin and admin_it.
POST /tenants/me/api-keys Creates a company key; returns the secret once only.
POST /tenants/me/api-keys/{id}/rotate Issues a new key with the same name and scopes and revokes the previous one.
DELETE /tenants/me/api-keys/{id} Revokes it immediately (soft-delete).
GET /tenants/me/api-keys/scopes Scope catalog for company keys.
GET / POST /me/api-keys Lists / creates personal keys (any signed-in member, only their own; at most 20 active).
POST / DELETE /me/api-keys/{id}/rotate · /me/api-keys/{id} Rotates / revokes a personal key.
GET /me/api-keys/scopes Scope catalog for personal keys.

/api-keys (and /api-keys/{id}/rotate, /api-keys/{id}) still exists as a deprecated alias of the company routes. Use /tenants/me/api-keys in new integrations.

Creation body: { "name": "...", "scopes": ["amadeus:invoke"], "expiresInDays": 90 } (name 1 to 100 characters, 1 to 100 scopes, expiresInDays between 1 and 365). Keys expire after 90 days by default and 365 days is the maximum. Only company keys can use "neverExpires": true (instead of expiresInDays); on a personal key it returns 400 never_expires_not_allowed. Other creation errors: 400 invalid_scope, 400 conflicting_expiry, 409 key_limit_reached.

Rotation is not zero-downtime. The server revokes the previous key first and only then creates the new one, so for a moment neither is valid, and the old key stops working at once. The new key keeps the same scopes and gets the default 90-day expiry (a company key that never expired stays that way). To avoid an interruption, create the new key, deploy it, then revoke the old one.

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 once the caller is identified. Every response includes headers so your client can self-regulate (the ones for the tighter of the two limits):

Header Meaning
X-RateLimit-Limit Cap of requests for the current window.
X-RateLimit-Remaining Requests left in the window.
X-RateLimit-Reset Moment (epoch in seconds) the window resets.
Retry-After Only on 429: seconds to wait before retrying.
X-Request-Id Unique request identifier; propagated in every response.

If you exceed the limit you get 429 with { "error": "rate_limit_exceeded", "retryAfterSec": <n> } and the Retry-After header. Respect that value with exponential backoff. The health probes (/livez, /healthz) are exempt from this limit and don’t carry X-RateLimit-* headers. If your integration needs a higher ceiling per workspace, write to support@aiginer.com. The X-Request-Id header travels in every response (if you send it, it’s reused; if not, it’s generated): always include it whenever you open a support ticket.

Summary of the surface covered on this page. Full routes on the https://api.sharasaas.com host (some carry /v1, others don’t; always use the route as it appears here).

Method Route Auth What it does
POST /v1/inference API key (or app session) Invokes an agent (Amadeus by default, or whichever you pass in agentSlug). Synchronous: the response already carries the result.
GET /runs/{run_id} App session only (no API key) Status and detail of an execution.
GET /runs App session only (no API key) Paginated list of executions.
GET /usage/current App session only (no API key) Period consumption (STU): quota, consumed, rollover.
GET /usage/timeseries · /usage/breakdown · /usage/by-agent · /usage/events App session only (no API key) Consumption over time, by alias or tool, by agent, and the raw event list.
POST /runs/{id}/cancel App session only (no API key) Cancels a run that is waiting for approval.
GET /v1/mcp/tenant/sse MCP token SSE transport for the per-tenant MCP bridge.
POST /v1/mcp/tenant/rpc MCP token JSON-RPC channel for the per-tenant MCP bridge.

The MCP bridge takes the key in the X-MCP-Tenant-Token header or in Authorization: Bearer, and the key must carry the scope mcp:tenant literally. Without a valid token it answers 401 unauthorized; it can also answer 429 rate_limit_exceeded or 429 too_many_connections.

The workspace orchestrator is called Amadeus: it receives your request, decides which departmental agent handles it and coordinates the work. With agentSlug you can address Amadeus (the default, if you omit it) or a specific agent by its slug. There’s no public agent catalog endpoint yet; check this documentation’s agent catalog for the available slugs. The slug stays the same whatever name and image pack your company uses (agentSlug: "patterson" works with any pack). A selectable agent your company has not chosen, or an additional agent it has not hired, is not available to it.

POST /v1/inference runs the turn and answers in the same call. It’s the only invocation endpoint: there’s no per-agent route, just an agentSlug field in the body. Body fields:

Field Type Notes
alias string · required One of Prelude · Sonata · Symphony · Concerto.
messages array (1 to 100) · required { "role": "user"|"assistant"|"system", "content": "..." }; each content 1 to 500,000 characters (the whole body is also capped at 512 KB).
agentSlug string · optional Target agent for the turn (1 to 64 characters). Defaults to amadeus. Your API key needs the scope for that agent.
powerMode string · optional normal (default) or low.
maxTokens integer · optional Between 1 and 32,000.
effort string · optional low · medium · high · x-high · max.
stream boolean · optional If true, the response arrives via SSE.
conversationId string · optional Groups several turns into the same thread (up to 200 characters).

The aliases Prelude, Sonata and Symphony are cloud models of growing capacity: Prelude (agile and economical), Sonata (balance) and Symphony (the most capable). Concerto is the local model of the Concerto Local plan and spends 0 STU. Choose based on task complexity; the alias is the only thing accepted to select a model (never a provider name). A body that fails validation returns 400 with error: "FST_ERR_VALIDATION".

The synchronous response (200) has this shape:

{
  "alias": "Sonata",
  "content": "The agent’s reply text…",
  "usage": {
    "inputTokens": 184,
    "outputTokens": 412
  },
  "status": "completed",
  "runId": "3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64"
}
Field Meaning
status How the turn ended: completed, failed, blocked or canceled. A failed or blocked turn is still HTTP 200: read this field, not only the status code.
runId Optional. Id of the run created for the turn (only if it was recorded).
toolOutcome Optional. { "rejected": [...], "expired": [...] } with the names of tools a person rejected or that expired in the turn.
herramientasEjecutadas Optional. Number of tools that ran successfully in the turn (only when greater than 0).
blockedReason Optional. Reason, when the turn didn’t complete.
motivoDeCorte Optional. Present only when the run stopped at one of its own limits (steps or turn spend) and not because something broke: tope_de_pasos, tope_de_gasto or sin_cierre.

Two field names are in Spanish. herramientasEjecutadas and motivoDeCorte keep their Spanish names in the API. They are stable: use them exactly as written.

With "stream": true the response is text/event-stream. You receive incremental fragments as data: {"delta":"…"} and a closing end event with the final status, the runId and the token counts. Every fragment passes through the redactor before being emitted. The stream may also carry other named events (for example usage with progress, or error): ignore the ones you don’t use.

data: {"delta":"Hello, "}

data: {"delta":"how can I "}

data: {"delta":"help you?"}

event: end
data: {"done":true,"status":"completed","runId":"3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64","alias":"Sonata","usage":{"inputTokens":184,"outputTokens":412}}

GET /usage/current returns the period status in STU (Shara’s consumption unit). It works with an app session only (it does not accept an API key) and is open reading for any member of the workspace. It does not expose absolute money figures. Fields:

Field Meaning
plan, planName Plan slug (first, pro, max, max_x5, max_x20, plan_local) and its display name.
year, month Label of the billing period.
stuBase, stuRollover Base quota (with additional agents included) and rollover carried over.
stuConsumed, stuRemaining, pctConsumed Consumed, remaining and percentage over base plus rollover.
daysElapsed, daysInMonth, projectedMonthEnd, projectedPct Linear projection to the end of the period.
status Traffic light: green (under 70%), yellow (70 to 90%), red (90 to 100%), blown (100% or more).
level Quota level: green, warning, critical, overage, grace, hardstop (see STU and quotas).
bannerMessage Text for display. Branch on level, not on this text.
allowInference, forceLowSpending, overageBilling What the current level implies.
resetAt When the period ends.
hasSnapshot Whether the period already has a usage record.
activeAlerts Active alerts for the period.
additionalAgents, addonAgentSlugs Additional agents purchased: how many and which.
{
  "plan": "pro",
  "planName": "Pro",
  "year": 2026,
  "month": 6,
  "stuBase": 140000000,
  "stuRollover": 20000000,
  "stuConsumed": 60000000,
  "stuRemaining": 100000000,
  "pctConsumed": 37.5,
  "daysElapsed": 10,
  "daysInMonth": 30,
  "projectedMonthEnd": 180000000,
  "projectedPct": 112.5,
  "status": "green",
  "level": "green",
  "bannerMessage": "…",
  "allowInference": true,
  "forceLowSpending": false,
  "overageBilling": false,
  "resetAt": "2026-07-01T00:00:00.000Z",
  "hasSnapshot": true,
  "activeAlerts": [],
  "additionalAgents": 0,
  "addonAgentSlugs": []
}

Without an active subscription it answers 403 no_active_subscription.

These routes also need an app session and are restricted to the people who can see the company’s figures (owners and administrators, and accounts with Amadeus enabled); others get 403 role_forbidden. Dates are YYYY-MM-DD; if you omit them the current period is used. Unlike /usage/current, they include billableCents (euro cents).

Route Query Returns
GET /usage/timeseries from, to, groupBy: day (default), alias or agent_slug { from, to, groupBy, points: [{ day, key, stuConsumed, billableCents, eventCount }] }
GET /usage/breakdown from, to, groupBy: alias or tool_name { from, to, totalStu, items: [{ key, stuConsumed, billableCents, eventCount, pctOfTotal }] }
GET /usage/by-agent from, to { from, to, totalStu, totalCents, items: [{ agentSlug, stuConsumed, billableCents, runCount, pctOfTotal }] }
GET /usage/events from, to, limit (1 to 10,000) { from, to, events: [...] }, one entry per usage event (alias, token counts, STU, tool, power mode). Meant for exporting to CSV.

POST /v1/inference is synchronous, so you don’t need to poll to get the result. Runs are the history of those executions, and you can query them with an app session only (GET /runs and GET /runs/{run_id} do not accept an API key). A run’s status takes one of these values:

status Meaning
in_progress The run is still executing.
completed Finished successfully.
failed Finished with an error. The detail carries the (redacted) reason.
canceled Canceled before completion.
suspended Parked, waiting for someone to approve an action. It can be canceled with POST /runs/{id}/cancel.

GET /runs lists executions with these filters: status, agent_slug (≤64 characters), includeInternal (true also shows the system’s internal runs, which are hidden by default), limit (1 to 100) and offset (≥0); the response includes total for pagination. GET /runs/{run_id} returns { run, steps[], toolCalls[] }; if the run doesn’t exist or isn’t visible to you, you get 404 run_not_found. What you see depends on your role: members who can’t see the whole workspace only see their own runs. Only redacted content is ever returned: never raw material from providers.

POST /runs/{id}/cancel cancels a run that is waiting for approval (status suspended) and returns { ok: true, run }. A run in any other state answers 409 run_not_cancelable; one that does not exist or is not yours, 404 run_not_found.

All errors are JSON with an error field (a stable string, meant for your logic) and, optionally, message (a human-readable text). Errors that go through the global handler (validation failures, an empty body, oversized chunked bodies, unexpected failures) also carry requestId; the X-Request-Id header is always present. Never parse message: branch on error. Canonical shape:

{
  "error": "string_in_snake_case",
  "message": "Human-readable description (optional).",
  "requestId": "req-a1b2c3"
}

A route that doesn’t exist answers with the framework’s shape: { "message": "Route not found", "error": "Not Found", "statusCode": 404 }.

Catalog of verified public codes:

error HTTP When
missing_bearer 401 Missing Authorization: Bearer … header.
invalid_token 401 Invalid or expired session token.
unknown_user 401 Valid session token, but the user doesn’t exist.
tenant_unresolved 401 Couldn’t resolve the caller’s workspace.
not_authenticated 401 No valid session/credential on a route that requires one.
api_key_not_found 401 API key with an unknown, revoked or expired prefix.
api_key_not_found 404 On the key management routes: that key id does not exist or is not yours.
already_revoked 409 The key you are revoking or rotating was already revoked.
api_key_invalid 401 API key whose hash fails to verify.
payment_not_completed 402 The checkout payment hasn’t been completed.
tenant_forbidden 403 Authenticated, but no membership in the requested tenant.
forbidden / role_forbidden 403 The action isn’t allowed for your role/key.
no_active_subscription 403 The workspace has no active subscription.
scope_required 403 The API key can’t invoke (message indicates the missing scope).
agent_scope_required 403 The key can invoke, but not this agent. Extra: agentSlug.
agent_disabled 403 The agent is paused. Extra: agentSlug, reason.
agent_unknown 403 The agentSlug doesn’t exist. Extra: agentSlug.
agent_not_in_plan 403 The agent isn’t available to your company on your plan. Extra: agentSlug.
agent_not_purchased 403 An additional agent that isn’t purchased in this account. Extra: agentSlug.
agent_not_allowed 403 The agent doesn’t belong to your department. Extra: agentSlug.
not_a_member 403 You’re not a member of the agent’s tenant. Extra: agentSlug.
agent_slug_required 400 A signed-in member who can’t use Amadeus must send agentSlug.
FST_ERR_VALIDATION 400 The body or parameters fail validation (for example in /v1/inference).
invalid_request / invalid_body 400 Body or parameters that fail validation on other routes.
empty_body 400 A JSON request with an empty body.
not_found 404 The resource doesn’t exist or isn’t visible to you.
expired 410 Single-use token/link already expired.
request_too_large 413 The body exceeds the limit (512 KB). Extra: limitBytes.
alias_plan_mismatch 422 You asked for a model alias above the ceiling of your plan (for example Sonata on First or Symphony on Pro). Ask for a lower one.
run_not_cancelable 409 Only a run waiting for approval can be canceled.
quota_exceeded 429 STU quota exhausted (hardstop). Extra: policy, pctConsumed, resetAt.
kill_switch 429 Active spending cut. Extra: kind, observedCents, thresholdCents, activeSince?.
rate_limit_exceeded 429 Request limit exceeded. Extra: retryAfterSec + Retry-After.
too_many_attempts 429 Too many failed attempts; wait before retrying.
internal_error 500 Unexpected server error. Includes requestId.
provider_unavailable 503 The model behind the alias is not available right now. Retry with backoff.
kill_switch_engine_error 503 The spending check could not be completed, so the request was not run. Retry in a few seconds.

Some 404s are more resource-specific (run_not_found, agent_not_found, member_not_found…), all with the same shape. For your client logic you can treat any *_not_found as “resource doesn’t exist”.

Several errors carry extra fields so your client can react without a second call. Exhausted quota (it only happens at the hardstop level, from 200% of your quota):

{
  "error": "quota_exceeded",
  "policy": "hardstop",
  "pctConsumed": 203.5,
  "resetAt": "2026-07-01T00:00:00.000Z"
}

Spending cut (kill switch). The caps are set in STU: by default 1M per run, 10M per hour and 80M per day (at the €5 per million rate, 500, 5,000 and 40,000 euro cents). An owner or administrator can edit or switch off each one. observedCents and thresholdCents are strings with the billable amount in euro cents:

{
  "error": "kill_switch",
  "kind": "day",
  "observedCents": "41250",
  "thresholdCents": "40000",
  "message": "…",
  "activeSince": "2026-06-16T08:12:00.000Z"
}

kind indicates the switch’s scope: run · hour · day · manual. On 429, respect Retry-After/resetAt and retry with backoff; on 5xx, retry with backoff and include the requestId if you open a support ticket.

Complete recipes on the https://api.sharasaas.com host. Replace shara_sk_live_… with your API key.

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"
  }'
import requests

BASE = "https://api.sharasaas.com"
API_KEY = "shara_sk_live_YOUR_API_KEY"

resp = requests.post(
    f"{BASE}/v1/inference",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "alias": "Sonata",
        "agentSlug": "amadeus",
        "messages": [
            {"role": "user", "content": "Summarize this week’s support news."}
        ],
        "effort": "medium",
    },
)
resp.raise_for_status()
data = resp.json()
print(data["status"], data["content"])  # the result is in this same response
const BASE = 'https://api.sharasaas.com';
const API_KEY = process.env.SHARA_API_KEY; // never in the browser

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) throw new Error(`HTTP ${res.status}: ${(await res.json()).error}`);
const data = await res.json();
console.log(data.status, data.content);

Shara has its own MCP server, at https://mcp.sharasaas.com, so you can use your company’s information from Cursor, Windsurf or any other MCP client. It exposes three things: your company’s indicators, the company memory and escalating a matter to an agent. Anything it writes waits for your approval, like any other action in Shara.

The owner, an admin or IT creates the token in Settings → MCP. Nobody else can. Then you add Shara to your client with the server address and the token in an Authorization header (the exact file depends on the client; in Cursor it is mcp.json):

{
  "mcpServers": {
    "shara": {
      "url": "https://mcp.sharasaas.com",
      "headers": {
        "Authorization": "Bearer <token>"
      }
    }
  }
}

Treat the token like a password: do not paste it into a repository or share it.

At general availability (GA) we’ll publish official SDKs for Node.js / TypeScript and Python, on top of the curl examples on this page. Until then, any standard HTTP client works: the API is plain REST, with standard headers and JSON. See SDK and examples.

Want early access to the API? Write to api@aiginer.com with your use case, or to support@aiginer.com to raise your per-workspace request limit.