> ## 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/chat/completions

> OpenAI Chat Completions-compatible drop-in endpoint. Point your existing openai SDK at HASP by changing the baseURL.

OpenAI Chat Completions-compatible endpoint. Coding agents, IDE integrations, and any code built against the `openai` SDK need only change the `baseURL` — request shape, response shape, and streaming chunks are OpenAI's exact wire format. Requires the `ai:chat` scope.

This surface is **stateless**: it never persists conversation content and does not accept a `conversation_id`. Audit events are still written for every request. If you need stored conversations, run management, or PHI entity metadata, use the native [`POST /v1/ai/chat`](/ai-api/reference/chat).

## Migrating from OpenAI

```javascript theme={null}
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.usehasp.com/v1',  // ← only this line changes
  apiKey: 'hasp_key_live_...',
});

const completion = await client.chat.completions.create({
  model: 'gpt-5.4',            // a HASP model id — see "Model selection" below
  messages: [{ role: 'user', content: 'Hello' }],
});
```

## Request

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

### Body parameters

| Parameter                 | Type             | Required | Description                                                                                      |
| ------------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------ |
| `model`                   | string           | Yes      | A HASP model id (see [Model selection](#model-selection)). Not the provider's public model name. |
| `messages`                | array            | Yes      | Conversation turns. Each has a `role` of `system`, `user`, `assistant`, or `tool`.               |
| `messages[].content`      | string           | —        | Message text. Required for `tool` messages.                                                      |
| `messages[].tool_call_id` | string           | —        | Required on `tool` messages; correlates the result to the assistant's tool call.                 |
| `messages[].tool_calls`   | array            | —        | Only valid on `assistant` messages; replays a prior tool call on the next turn.                  |
| `tools`                   | array            | No       | Function-tool definitions: `{"type": "function", "function": {...}}`.                            |
| `tool_choice`             | string \| object | No       | Standard OpenAI tool-choice control.                                                             |
| `max_tokens`              | integer          | No       | Max output tokens (1–32000). Defaults to your org's per-request cap.                             |
| `stream`                  | boolean          | No       | Stream via SSE. **Default `false`.**                                                             |
| `temperature`             | number           | No       | 0–2.                                                                                             |

**Accepted but ignored.** `top_p`, `n`, `frequency_penalty`, `presence_penalty`, `stop`, and `user` are validated for shape (so existing SDK payloads don't error) but do not affect inference — HASP's inference request does not model them.

Unlike the native `/v1/ai/chat` endpoint, `stream` **defaults to `false`** here, matching OpenAI's default.

## Model selection

The `model` field takes a **HASP registry model id**, not the provider's public name. The same identifiers work on every HASP surface:

| 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 |

There is no requirement to pick an OpenAI model on this surface — you can drive a Claude model through the OpenAI wire format. Premium models require org enablement; a request for a model your org has not enabled returns a `400 invalid_request_error` with `hasp_code` `MODEL_ACCESS_DENIED`. See [Model Selection](/ai-api/concepts/model-selection) for cost/access details.

## Non-streaming response

When `stream` is omitted or `false`, the response is a single `chat.completion` object:

```json theme={null}
{
  "id": "chatcmpl_01JQMSG000000000000000000",
  "object": "chat.completion",
  "created": 1775692800,
  "model": "gpt-5.4",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The HIPAA minimum necessary standard requires..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 74,
    "total_tokens": 92
  }
}
```

When the model calls a tool, `message.content` is `null` and `message.tool_calls` carries the calls; `finish_reason` is `tool_calls`.

## Streaming response (SSE)

When `stream: true`, the response is a `text/event-stream` of `chat.completion.chunk` objects in OpenAI's exact format — the same shape the `openai` SDK's streaming iterator expects:

```
data: {"id":"chatcmpl_...","object":"chat.completion.chunk","created":1775692800,"model":"gpt-5.4","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","created":1775692800,"model":"gpt-5.4","choices":[{"index":0,"delta":{"content":"The HIPAA"},"finish_reason":null}]}

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","created":1775692800,"model":"gpt-5.4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl_...","object":"chat.completion.chunk","created":1775692800,"model":"gpt-5.4","choices":[],"usage":{"prompt_tokens":18,"completion_tokens":74,"total_tokens":92}}

data: [DONE]
```

The first chunk carries the assistant role, content deltas stream in the middle, a penultimate chunk sets `finish_reason`, a final chunk carries `usage`, and the stream terminates with `data: [DONE]`. Tool calls stream as `delta.tool_calls[]` entries with a stable zero-based `index`.

## Compliance behavior

All HASP compliance checks apply — PHI Guard, BAA enforcement, and credit metering — identically to every other surface. Compliance is a property of your organization, not the endpoint shape.

* **PHI Guard** runs on all messages per your org's `phi_mode`. In `redact` mode PHI is replaced with placeholders before the upstream call; in `block` mode the request is rejected.
* **BAA required.** No active BAA → a `permission_error` with `hasp_code` `BAA_REQUIRED`.
* **Credit metering.** Credits are deducted after each successful response.

## Errors

Errors are returned in OpenAI's error envelope. HASP's richer error vocabulary maps into OpenAI's error `type` set, with the original HASP code preserved as `code` (lowercased) and `hasp_code`:

```json theme={null}
{
  "error": {
    "message": "Model 'gpt-5.5-pro' is not in the allowed model list for organization <org-id>.",
    "type": "invalid_request_error",
    "param": null,
    "code": "model_access_denied",
    "hasp_code": "MODEL_ACCESS_DENIED"
  }
}
```

| HTTP | OpenAI `type`           | Example condition (`hasp_code`)                                                                                                                             |
| ---- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401  | `authentication_error`  | Missing or revoked token (`INVALID_API_KEY`)                                                                                                                |
| 403  | `permission_error`      | No active BAA (`BAA_REQUIRED`); credits exhausted (`CREDITS_EXHAUSTED`); AI paused (`AI_PAUSED`); feature not HIPAA-eligible (`FEATURE_NOT_HIPAA_ELIGIBLE`) |
| 400  | `invalid_request_error` | Model not enabled or unknown (`MODEL_ACCESS_DENIED`); PHI blocked (`PHI_BLOCKED`); retired model (`MODEL_RETIRED`)                                          |
| 429  | `rate_limit_error`      | RPM or daily limit exceeded (`RATE_LIMITED`)                                                                                                                |
| 500  | `api_error`             | Upstream model provider error (`INFERENCE_UPSTREAM_FAILURE`)                                                                                                |

During a stream, a mid-run failure is emitted as a terminal `data:` line carrying the same `error` envelope rather than an HTTP status change.
