> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usehasp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/ai/chat

> Native HASP streaming chat endpoint. Streams by default. Requires the ai:chat scope.

Native HASP chat endpoint. Streams by default. Requires the `ai:chat` scope.

## Request

```
POST https://api.usehasp.com/v1/ai/chat
Authorization: Bearer hasp_api_live_...
Content-Type: application/json
```

### Body parameters

| Parameter         | Type    | Required | Description                                                                                                                           |
| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `message`         | string  | Yes      | The user message content.                                                                                                             |
| `model`           | string  | No       | Model ID. Defaults to `claude-sonnet-4-6`. See [Models](#models).                                                                     |
| `stream`          | boolean | No       | Stream the response via SSE. Default `true`.                                                                                          |
| `conversation_id` | string  | No       | ULID of an existing conversation to continue. Omit to start a new conversation.                                                       |
| `store`           | boolean | No       | Persist the request/response content. Default `true`. Set `false` for stateless requests — audit events are still written regardless. |
| `system`          | string  | No       | System prompt. Overrides the org default if set.                                                                                      |
| `max_tokens`      | integer | No       | Maximum output tokens.                                                                                                                |

## Non-streaming response

When `stream: false`:

```json theme={null}
{
  "success": true,
  "data": {
    "message": {
      "id": "msg_01JQMSG000000000000000000",
      "role": "assistant",
      "content": "The HIPAA minimum necessary standard requires..."
    },
    "conversation_id": "conv_01JQCONV00000000000000000"
  },
  "meta": {
    "request_id": "req_01JQREQ000000000000000000",
    "usage": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 18,
      "output_tokens": 74,
      "sonnet_equivalent_tokens": 92,
      "cost_usd": 0.000935
    }
  }
}
```

## Streaming response (SSE)

When `stream: true` (default), the response is a standard `text/event-stream`. Each event follows this envelope:

```
event: <type>
data: {...}

```

Each event is a standard SSE frame: an `event:` line naming the type and a `data:` line carrying a flat JSON payload. There is no envelope object and no `id:` line — the payload fields listed below are at the top level of `data`.

### Event sequence

A successful run emits:

1. `run.started` — inference initiated
2. `message.delta` (repeated) — content chunks as they arrive
3. `tool_call` / `provider_tool.completed` (as they occur) — tool activity during the turn
4. `run.completed` — run finished, with final usage and credits

`heartbeat` may be interleaved at any point to keep the connection alive. On failure, `error` is emitted instead of `run.completed`.

### Event types

#### `run.started`

```json theme={null}
{
  "request_id": "req_01ABC123DEF456GHI789JKL012",
  "message_id": "01ABC123DEF456GHI789JKL012",
  "model": "claude-sonnet-4-6"
}
```

#### `message.delta`

```json theme={null}
{ "text": "The HIPAA" }
```

#### `tool_call`

```json theme={null}
{
  "id": "toolu_01ABC...",
  "name": "web.search",
  "arguments": { "query": "HIPAA breach notification rule" }
}
```

#### `provider_tool.completed`

```json theme={null}
{ "tool_type": "web_search", "item_id": "ws_01ABC..." }
```

#### `heartbeat`

```json theme={null}
{}
```

Emitted when the stream has been idle, so intermediaries do not drop the connection. Carries no payload — ignore it.

#### `run.completed`

```json theme={null}
{
  "request_id": "req_01ABC123DEF456GHI789JKL012",
  "message_id": "01ABC123DEF456GHI789JKL012",
  "credits_consumed": 0.006,
  "usage": {
    "input_tokens": 18,
    "output_tokens": 74,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  },
  "finish_reason": "end_turn",
  "stop_reason_raw": "end_turn"
}
```

`finish_reason` is the normalized reason; `stop_reason_raw` is the provider-native value, which is finer-grained (`finish_reason` collapses end-of-turn and stop-sequence together).

### Error events

There is no `run.failed` event. A failure mid-stream emits `error` and the stream ends:

```
event: error
data: {"code":"PHI_BLOCKED","message":"...","retryable":false,"failure_layer":"gateway"}
```

| Field           | Description                                                                       |
| --------------- | --------------------------------------------------------------------------------- |
| `code`          | An [error code](/ai-api/reference/errors).                                        |
| `message`       | Human-readable description.                                                       |
| `retryable`     | Whether retrying the same request may succeed.                                    |
| `failure_layer` | Where the failure occurred — e.g. `gateway` for policy and compliance rejections. |

### Consuming the stream (JavaScript)

```javascript theme={null}
const response = await fetch('https://api.usehasp.com/v1/ai/chat', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer hasp_api_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ message: 'Hello', stream: true }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let eventType = null;

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop();

  for (const line of lines) {
    if (line.startsWith('event: ')) {
      eventType = line.slice(7).trim();
    } else if (line.startsWith('data: ')) {
      const data = JSON.parse(line.slice(6));
      if (eventType === 'message.delta') {
        process.stdout.write(data.text);
      } else if (eventType === 'error') {
        throw new Error(`${data.code}: ${data.message}`);
      }
    }
  }
}
```

Switch on the `event:` line, not on a field inside `data` — the payload has no `type` field.

### Cancellation

Close the SSE connection to cancel. The server detects the disconnect and halts the upstream call. No additional API call is needed, and no further events are emitted.

## Models

The endpoint serves both Anthropic and OpenAI models.

| Model ID            | Provider  | Relative cost | Access   |
| ------------------- | --------- | ------------- | -------- |
| `claude-sonnet-4-6` | Anthropic | 1.0×          | Standard |
| `claude-haiku-4-5`  | Anthropic | 0.3×          | Standard |
| `claude-opus-4-6`   | Anthropic | 1.7×          | Premium  |
| `claude-opus-4-7`   | Anthropic | 1.7×          | Premium  |
| `gpt-5.5`           | OpenAI    | 1.7×          | Premium  |
| `gpt-5.5-pro`       | OpenAI    | 10.0×         | Premium  |
| `gpt-5.4`           | OpenAI    | 0.8×          | Standard |
| `gpt-5.4-mini`      | OpenAI    | 0.25×         | Standard |
| `gpt-5.3-codex`     | OpenAI    | 0.6×          | Standard |

Premium (opt-in) models must be enabled by an org admin in **Settings → AI Workspace → Models** before requests using them are accepted. Requests for a model your org has not enabled return `403 MODEL_ACCESS_DENIED`.

## Error codes

| Code                         | HTTP | Description                                                               |
| ---------------------------- | ---- | ------------------------------------------------------------------------- |
| `INVALID_API_KEY`            | 401  | Missing or revoked token                                                  |
| `BAA_REQUIRED`               | 402  | No active BAA on the org                                                  |
| `AI_CREDITS_EXHAUSTED`       | 402  | Org has exhausted its credit allotment                                    |
| `MISSING_SCOPE`              | 403  | Key lacks `ai:chat` scope                                                 |
| `PHI_BLOCKED`                | 403  | Message contains PHI and `phi_mode=block` is set                          |
| `FEATURE_NOT_HIPAA_ELIGIBLE` | 403  | Requested AI feature is not HIPAA-eligible and is disabled                |
| `MODEL_ACCESS_DENIED`        | 403  | Requested model is not enabled for this org, or the identifier is unknown |
| `RATE_LIMITED`               | 429  | RPM or daily limit exceeded — check `Retry-After`                         |
| `INFERENCE_UPSTREAM_FAILURE` | 502  | Upstream model provider error — retryable                                 |
