SHAI

Docs

The API, and the MCP server

Everything here goes through the same code the SHAI portal uses — the same tenancy rules, the same metering, the same approvals. A run started through the API parks for your approval before anything irreversible, exactly as one started in chat does.

v1.0.0OpenAPI 3.1.0MCP 2026-07-28

Getting a key

Keys are made at Settings → API keys, by an owner or an admin. A key is shown once: SHAI stores only a hash of it and cannot show it to you again. Send it as a bearer token:

curl https://<your SHAI>/api/v1/runs \
  -H "Authorization: Bearer <your key>"

Never in a query string. A credential in a URL ends up in access logs, browser history and Referer headers, so this API does not accept one there.

You choose an expiry when you make a key, and it cannot be changed afterwards — a date you can push back is not a date. Keys made without one do not expire, which is the default. Settings → API keys shows every key’s expiry and suggests replacing a key older than 90 days that has none; that is a suggestion and nothing else, and no key is ever refused for its age alone.

Scopes

Chosen when a key is made and never widened afterwards. A key without runs:write cannot start anything, and therefore cannot spend anything.

  • runs:readRead runs and their live output.
  • runs:writeStart a run. A run started this way spends money and parks for approval exactly like one started in chat.
  • deliverables:readList the files SHAI has produced and read their metadata.
  • tasks:readRead the scheduled tasks on this account.
  • tasks:writeCreate a scheduled task. It arrives paused, like every proposed task.
  • usage:readRead what this account has spent, by day and by provider.

Operations

7 of them, each available twice: as an HTTP endpoint and as an MCP tool of the same name. The machine-readable description is at /api/v1/openapi.json.

start_run

runs:write

POST /api/v1/runs

Starts a run of a named agent in a workspace. This is the same path SHAI's own chat uses, so the run is metered against your account, it is held to your daily spend ceiling, and anything irreversible still stops and asks for approval. If the agent needs a tool you have not connected, the run starts anyway and the missing tools come back in `needsYou` — SHAI parks the part that needs them and does the rest.

get_run

runs:read

GET /api/v1/runs/{id}

Returns the run's row — status, the estimated and actual cost, and `needsConnector` when it is parked waiting for a tool to be connected. A run id from another account is reported as not found rather than refused, because saying which ids exist elsewhere is itself a leak.

list_runs

runs:read

GET /api/v1/runs

Every run on the account, or in one workspace when `businessId` is given. Newest first, up to `limit`.

list_deliverables

deliverables:read

GET /api/v1/deliverables

Documents, spreadsheets, decks and PDFs an agent has produced, with their metadata and where the bytes are stored. The bytes themselves are fetched from the signed URL on each row, not from this call.

list_tasks

tasks:read

GET /api/v1/tasks

Every scheduled task, with its cadence in words, what it costs per month at its current settings, and whether it is paused.

create_task

tasks:write

POST /api/v1/tasks

Creates a scheduled task. Send `paused: true` — which is the default here — if you want it to arrive the way a proposed task does, doing nothing until somebody looks at it. A task that is not paused will start spending on its next due time, against the same daily ceiling every other run obeys.

usage

usage:read

GET /api/v1/usage

The same report the usage screen shows and the same one SHAI answers with in chat: totals for the period, the breakdowns by workspace, agent, person and model, and a burn-rate forecast. Tokens are billed to you by your own model vendor; SHAI adds no markup, so these are costs rather than charges.

One more endpoint has no MCP tool because it is a stream rather than a call: GET /api/v1/runs/<id>/events is the run’s live output as server-sent events, on the runs:read scope. It closes itself before the platform’s function limit and hands back a cursor to resume from.

The MCP server

SHAI serves its own MCP endpoint at /api/mcp, so Claude Desktop, Cursor and your own Claude Code can drive SHAI directly. It exposes 7 tools — the same operations above — authenticated with the same API key as a bearer token.

In Claude Code:

claude mcp add --transport http shai \
  https://<your SHAI>/api/mcp \
  --header "Authorization: Bearer <your key>"

In Claude Desktop or Cursor, in the MCP servers section:

{
  "mcpServers": {
    "shai": {
      "type": "http",
      "url": "https://<your SHAI>/api/mcp",
      "headers": {
        "Authorization": "Bearer <your key>"
      }
    }
  }
}

Replace <your key> with a key from Settings → API keys and <your SHAI> with this deployment’s own address.

It is stateless: POST only, no session, no server-initiated stream. It implements MCP 2026-07-28 and answers the initialize handshake of 2025-06-18 and 2025-03-26 for clients that still speak it. A GET or DELETE answers 405, which is what a client of those revisions is told to expect from a server that offers no stream.

Limits, and what a refusal looks like

Every response is JSON with a stable error code and a sentence. Branch on the code, never on the sentence.

  • 401 missing_key — no Authorization header.
  • 401 invalid_key — not a key, or revoked, or expired. The same answer for all three, so a stranger holding a stolen key learns nothing from the difference.
  • 403 missing_scope — a real key without this operation’s scope.
  • 404 not_found — including a run that belongs to another account, because saying which ids exist elsewhere is itself a leak.
  • 429 rate_limited — with Retry-After in seconds. The ceiling is charged per account and per key, so one runaway script cannot use up the allowance the dashboard is running on.

A key with the prefix shai_sk is recognisable on sight, which is what lets a secret scanner find one that was committed by accident.