> For the complete documentation index, see [llms.txt](https://docs.saharaai.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.saharaai.com/sorin/agent-as-a-service.md).

# Agent as a Service (AaaS)

Build with Sorin’s full financial intelligence agent over HTTP. Send a question, let Sorin handle the underlying market analysis, tool use, and reasoning, and return a finished answer to your app.

Agent as a Service (AaaS) lets you call Sorin’s full financial intelligence agent from your application over HTTP. Send a question and Sorin handles the underlying market analysis, tool calls, and reasoning, then returns a finished answer. No agent orchestration is required on your side.

Use AaaS when you want Sorin to handle the intelligence workflow end to end. If you want to bring Sorin’s data, intelligence, and tools into an agent or workflow you control, use [Tools as a Service (TaaS) ](/sorin/tools-as-a-service.md)instead.

|                  | Tools as a Service                                                                                                                     | Agent as a Service                                                                                                                      |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Agent**        | Yours                                                                                                                                  | Sorin                                                                                                                                   |
| **What you get** | Sorin's data and analysis tools                                                                                                        | Sorin's full agent and reasoning                                                                                                        |
| **Best for**     | Bringing Sorin’s real-time market intelligence, project data, quantitative analysis, and trading tools into your own agent or workflow | Embedding Sorin’s full financial intelligence agent into your product, with market analysis, tool use, and reasoning handled end to end |
| **Interface**    | MCP                                                                                                                                    | HTTP                                                                                                                                    |

**TaaS:** your agent decides what to research, which tools to call, and how to combine the results. See [Tools as a Service](broken://pages/c5daafe7922a311a0d15ff33be70f88308501b2a).

**AaaS:** your application calls Sorin and Sorin decides what data and tools to use, performs the analysis and reasoning, and returns the answer.

## Getting a key

In the [Sorin app](https://heysorin.ai/) open **Profile → API Key → Agent as a Service → New key**.&#x20;

You can have up to 5 active keys. The full key is shown only once when it is created and looks like `sk-sahara-agent-…`.&#x20;

Agent keys authorize spending from your credit balance, so keep them server-side and never expose them in browser or mobile application code.

```
Authorization: Bearer sk-sahara-agent-...
```

## Base URL

Agent as a Service uses a dedicated API host. All Agent API paths are relative to the base URL below, so switching environments only requires changing the base URL.

| Environment | Base URL                      |                 |
| ----------- | ----------------------------- | --------------- |
| Production  | `https://api.heysorin.ai`     | not serving yet |
| PRE         | `https://api-pre.heysorin.ai` | not serving yet |
| DEV         | `https://api-dev.heysorin.ai` | live            |

{% hint style="info" %}
The Agent API path is always `/v1/agent/...` on the host above — do not add `/api` or `/developer` prefixes. Those routes belong to Sorin’s browser-session gateway and do not accept Agent API keys.
{% endhint %}

## Run the agent

Every interaction with Sorin starts as a run. Send a user message, choose a mode, and optionally provide a `chatId` to continue an existing conversation or enable streaming to receive the response as it is generated.

```
POST /v1/agent/runs
```

| Field       | Required | Notes                                                                |
| ----------- | -------- | -------------------------------------------------------------------- |
| `message`   | yes      | `{"role": "user", "content": "..."}`, up to 16,000 characters        |
| `mode`      | yes      | `fast` or `expert`                                                   |
| `stream`    | no       | `true` for Server-Sent Events; defaults to `false`                   |
| `chatId`    | no       | Continue an existing conversation; a new one is created when omitted |
| `requestId` | no       | Your idempotency key, `^[A-Za-z0-9._:-]+$`, up to 128 characters     |

**How to retry safely:** replaying a `requestId` with the same body returns the original run and does not charge twice. Reusing a `requestId` with a different body returns a `409`.

## Agent examples

### Fast mode — one question, one answer

`fast` is optimized for lower latency and cost. Use it for everyday questions and requests that do not require extended analysis.

```bash
curl -X POST https://api.heysorin.ai/v1/agent/runs \
  -H "Authorization: Bearer $SAHARA_AGENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
        "message": {"role": "user", "content": "What moved BTC today?"},
        "mode": "fast",
        "requestId": "btc-daily-2026-08-19"
      }'
```

```json
{
  "id": "run_...",
  "requestId": "btc-daily-2026-08-19",
  "chatId": "chat_...",
  "messageId": "msg_...",
  "status": "completed",
  "mode": "fast",
  "stream": false,
  "content": "...",
  "usage": { "input_tokens": 1200, "output_tokens": 400, "charged_credits": 5 },
  "billing_status": "charged",
  "created_at": "2026-08-19T10:00:00Z",
  "completed_at": "2026-08-19T10:00:04Z"
}
```

`content` contains the final answer only. Runtime status narration is stripped from non-streamed responses.

### Expert mode — deeper research

`expert` allows Sorin to perform more analysis, tool calls, and cross-checking before answering. Use it when depth matters more than latency or cost.

```bash
curl -X POST https://api.heysorin.ai/v1/agent/runs \
  -H "Authorization: Bearer $SAHARA_AGENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
        "message": {
          "role": "user",
          "content": "Is ETH perp funding confirming the spot trend on 4h? Show what you checked."
        },
        "mode": "expert"
      }'
```

### Multi-turn — keep the conversation

Every run returns a `chatId`. Pass that same `chatId` with your next message to continue the conversation. You do not need to resend previous messages; Sorin keeps the conversation history server-side.

Conversations are scoped to the API key that created them. Attempting to continue a conversation with another key, even on the same account, returns `404`.

```bash
curl -X POST https://api.heysorin.ai/v1/agent/runs \
  -H "Authorization: Bearer $SAHARA_AGENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
        "message": {"role": "user", "content": "And what about ETH?"},
        "mode": "fast",
        "chatId": "chat_..."
      }'
```

Later turns generally use more input tokens, increasing cost, because the existing conversation history is automatically included as context.

### Streaming — token by token

```bash
curl -N -X POST https://api.heysorin.ai/v1/agent/runs \
  -H "Authorization: Bearer $SAHARA_AGENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"message":{"role":"user","content":"Summarise SOL today."},"mode":"fast","stream":true}'
```

```
event: run.created
data: {"type":"run.created","run_id":"run_...","status":"queued"}

event: run.in_progress
data: {"type":"run.in_progress","run_id":"run_...","status":"in_progress"}

event: output.delta
data: {"type":"output.delta","run_id":"run_...","delta":"SOL "}

event: run.completed
data: {"type":"run.completed","run_id":"run_...","status":"completed","usage":{...}}
```

Use the SSE event name to determine the run state:

* `run.completed` — the run completed successfully
* `run.failed` — the run failed
* `run.canceled` — the run was canceled

If the stream closes without a terminal event, treat it as a transport interruption rather than a completed run.

During streaming, Sorin’s status narration is delivered before the answer, separated by a blank line. Treat text before that blank line as status narration, or fetch the run afterward if you only need the clean final `content`.

### TypeScript

```ts
const res = await fetch(`${BASE_URL}/v1/agent/runs`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SAHARA_AGENT_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: { role: 'user', content: 'What moved BTC today?' },
    mode: 'fast',
    requestId: `btc-${new Date().toISOString().slice(0, 10)}`,
  }),
});

if (res.status === 402) throw new Error('Out of credits');
const run = await res.json();
console.log(run.content, run.usage.charged_credits);
```

### Python

```python
import os, requests

run = requests.post(
    f"{BASE_URL}/v1/agent/runs",
    headers={"Authorization": f"Bearer {os.environ['SAHARA_AGENT_KEY']}"},
    json={
        "message": {"role": "user", "content": "Find recent listing events for Binance."},
        "mode": "fast",
    },
    timeout=180,
).json()

print(run["content"])
```

### Cancelling

`POST /v1/agent/runs/{id}/cancel` cancels a **streamed** run in progress within about a second. The stream ends with `run.canceled` and you are charged only for the output produced up to that point, so stopping early costs less than letting the run finish.

Start a run with `"stream": true` whenever your application needs the ability to stop it before completion.

## Other endpoints

|                                   |                                                                                                                                                                                                                                                   |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /v1/agent/runs/{id}`         | Fetch a run; only the key that created it can read it                                                                                                                                                                                             |
| `POST /v1/agent/runs/{id}/cancel` | Cancel a run in flight                                                                                                                                                                                                                            |
| `GET /v1/agent/usage`             | Returns what this key has been charged, newest first. Paginate with `page` / `size`. Each row includes a `billing_status` of `charged`, `refunded`, or `pending`. A pending row has not settled, so its `charged_credits` value is not yet final. |
| `GET /v1/agent/balance`           | Returns `{"credits": N, "held_credits": N}` —  your remaining credits and credits currently held by runs in flight.                                                                                                                               |

## Billing

AaaS runs are charged against your Sorin app credit balance based on the tokens they use. Credits are held when a run starts and settled when it finishes.

The available hold also limits how much output a run can generate, preventing a run from taking your balance negative.

#### What each ending costs

* **Completed run:** charged for the tokens it used
* **Canceled run:** charged only for output produced before cancellation
* **Run produces no output:** refunded in full
* **Insufficient balance to fund a useful answer:** returns `402` before anything is charged

Every run also carries an input overhead from Sorin’s own instructions and tools, regardless of the length of your question. Combining related questions into one call can therefore use fewer credits than splitting them across separate runs.

Multi-turn conversations add conversation history to that input on each turn, so usage generally increases as the conversation gets longer.

### Knowing when a charge is final

Every run and usage row includes `billing_status`:

* `charged` — the charge is final
* `refunded` — the refund is final
* `pending` — settlement is still in progress

Reconcile usage against `charged` rows. For a run canceled early, final usage appears in the stream’s terminal event and on a subsequent `GET /v1/agent/runs/{id}`.

***

## Errors

| Status | Meaning                                                                                                    |
| ------ | ---------------------------------------------------------------------------------------------------------- |
| `400`  | Malformed body — missing `mode` or `message`, bad `requestId` charset                                      |
| `401`  | Missing, unknown, or revoked key                                                                           |
| `402`  | Not enough credits                                                                                         |
| `404`  | Unknown run id, a `chatId` belonging to another key, or Agent as a Service is not enabled for your account |
| `409`  | `requestId` reused with a different body                                                                   |
| `429`  | Rate limited or the daily run quota has been used up                                                       |
| `503`  | The agent runtime is unavailable; safe to retry with the same `requestId`                                  |

Every response carries `X-Trace-Id`. Include it in support requests.

## Limits

* 5 active API keys per account
* 60 runs per key per minute, 120 per account per minute.
* Free plans have a daily run limit per account, resetting at **`00:00 UTC`.** Creating additional keys does not increase this limit.&#x20;
* An active PLUS plan removes the daily run limit. Runs still consume credits; plan status changes how often you may call AaaS, not whether calls consume credits.
* AaaS and [Tools as a Service](broken://pages/c5daafe7922a311a0d15ff33be70f88308501b2a) use separate daily allowances. TaaS queries do not consume AaaS runs, and AaaS runs do not consume TaaS queries.
* 16,000 characters of input per request
* 180 seconds per streamed run
