> ## Documentation Index
> Fetch the complete documentation index at: https://abstract-pbc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat completions

> Call the LLM models with the OpenAI Chat Completions API, whole or streamed.

The LLM models speak the OpenAI Chat Completions API at `POST https://api.abstract.inc/v1/chat/completions`.
The `model` in the body selects the model.

| Model id | Weights |
| - | - |
| `kimi-k3.1-pro` | Kimi K3.1 Pro |
| `glm-5.3` | GLM-5.3 |
| `glm-5.3-flash` | GLM-5.3-Flash |
| `deepseek-v4.1-flash` | DeepSeek V4.1 Flash |

Each model returns its answer in the HTTP response, or as server-sent events with `"stream": true`.

`GET /v1/models` lists these ids.
Use an id exactly as written.

## Request

```sh theme={"theme":"css-variables"}
curl -s https://api.abstract.inc/v1/chat/completions \
  -H "Authorization: Bearer $(abstract token)" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"model":"glm-5.3-flash","messages":[{"role":"user","content":"Summarise RFC 8628 in three sentences."}]}'
```

**model** `string` *required*

One of the ids above.

**messages** `array` *required*

At least one message. `role` is `system`, `developer`, `user` or `assistant`. `content` is a
string or an array of `text` parts. A `user` message can also carry `image_url` parts, each a
base64 `data:` URL; any other URL is refused.

**max\_completion\_tokens** `integer`

A positive integer, at most 65,536, which is also the limit when you send none. It counts the
model's reasoning as well as its answer, and the API holds its cost on your account until the call
ends. `max_tokens` is accepted with the same meaning.

**stream** `boolean`

`true` returns live server-sent events.

**response\_format** `object`

`text`, `json_object`, or `json_schema` with a `json_schema` object that has a `name`.

**reasoning\_effort** `string`

Passed to the model as its reasoning effort.

**temperature, top\_p, user, metadata, prompt\_cache\_key** `various`

As in the OpenAI API. `metadata` values are strings.

**n** `integer`

Only `1`.

The API refuses any other field, `tools` and `stop` included, with HTTP 422 `invalid_request` before it reserves any cost.
The request body is at most 2 MiB.

## Answer

```json theme={"theme":"css-variables"}
{
	"id": "resp_8f2c1a",
	"object": "chat.completion",
	"created": 1790000000,
	"model": "glm-5.3-flash",
	"choices": [
		{
			"index": 0,
			"message": {
				"role": "assistant",
				"content": "RFC 8628 defines the OAuth 2.0 device authorization grant.",
				"reasoning_content": "The reader asks for a summary."
			},
			"finish_reason": "stop"
		}
	],
	"usage": {
		"prompt_tokens": 14,
		"completion_tokens": 96,
		"total_tokens": 110,
		"prompt_tokens_details": { "cached_tokens": 0 }
	}
}
```

* `reasoning_content` is present when the model reports its reasoning.
* `finish_reason` is `stop`, or `length` when the answer reached `max_completion_tokens`.
* The `model` field in every answer is the public id you sent.

## Streaming

With `"stream": true`, the answer arrives as server-sent events, in this order:

1. `chat.completion.chunk` events with `delta.content`, and `delta.reasoning_content` when the model reports it.
2. One chunk with `finish_reason`.
3. One chunk with `usage` and an empty `choices` array.
4. `data: [DONE]`.

## Read a completion later

`GET /v1/chat/completions/{id}` returns a completion your account created.

```sh theme={"theme":"css-variables"}
curl -s https://api.abstract.inc/v1/chat/completions/resp_8f2c1a \
  -H "Authorization: Bearer $(abstract token)"
```

| Status | Meaning |
| - | - |
| 200 | The finished `chat.completion`. |
| 502 | `upstream_failed`: the model did not complete the request. |

Only the account that created a completion can read it.
Any other account gets HTTP 404 `not_found`.
A completion from a retired model reads back from its kept answer, and one whose answer was never kept answers HTTP 404 `not_found`.

## Idempotency-Key

`Idempotency-Key` is an optional header of 1 to 200 characters from `A-Z a-z 0-9 _ . / : -`.

* The same POST with the same key returns the same answer and never starts a second job. `stream` can differ between the two.
* The same key with a different body returns HTTP 409 `idempotency_conflict`.
* Use a new key for a new request.
* An accepted POST answers with an `Idempotency-Key` header. The API generates one when you send none.

Send a key whenever a client can retry, so a retry never starts a second job.

When every replica serving the model is busy, the POST answers HTTP 429 `no_replica_free` with a `Retry-After` header and reserves nothing. Wait that many seconds, then send it again with the same key.

## OpenAI SDK

Set the base URL to `https://api.abstract.inc/v1` and read the key from `ABSTRACT_API_KEY`.

```python answer.py theme={"theme":"css-variables"}
import os
import uuid

from openai import OpenAI

client = OpenAI(base_url="https://api.abstract.inc/v1", api_key=os.environ["ABSTRACT_API_KEY"])

completion = client.chat.completions.create(
    model="glm-5.3",
    messages=[{"role": "user", "content": "Summarise RFC 8628 in three sentences."}],
    extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)
print(completion.choices[0].message.content)
```

```typescript answer.ts theme={"theme":"css-variables"}
import OpenAI from "openai"

const client = new OpenAI({
  baseURL: "https://api.abstract.inc/v1",
  apiKey: process.env.ABSTRACT_API_KEY,
})

const completion = await client.chat.completions.create(
  {
    model: "glm-5.3",
    messages: [{ role: "user", content: "Summarise RFC 8628 in three sentences." }],
  },
  { headers: { "Idempotency-Key": crypto.randomUUID() } },
)
console.log(completion.choices[0].message.content)
```

```python stream.py theme={"theme":"css-variables"}
import os
import uuid

from openai import OpenAI

client = OpenAI(base_url="https://api.abstract.inc/v1", api_key=os.environ["ABSTRACT_API_KEY"])

stream = client.chat.completions.create(
    model="glm-5.3-flash",
    messages=[{"role": "user", "content": "Write a haiku about a robot arm."}],
    stream=True,
    extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
```

```typescript stream.ts theme={"theme":"css-variables"}
import OpenAI from "openai"

const client = new OpenAI({
  baseURL: "https://api.abstract.inc/v1",
  apiKey: process.env.ABSTRACT_API_KEY,
})

const stream = await client.chat.completions.create(
  {
    model: "glm-5.3-flash",
    messages: [{ role: "user", content: "Write a haiku about a robot arm." }],
    stream: true,
  },
  { headers: { "Idempotency-Key": crypto.randomUUID() } },
)
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "")
}
process.stdout.write("\n")
```

Run a program with the key in its environment only, for example `ABSTRACT_API_KEY="$(abstract token)" python answer.py`.

## Usage

A finished completion carries `usage` with `prompt_tokens`, `prompt_tokens_details.cached_tokens` and `completion_tokens`.
The completion is billed from those counts at the model's per-million-token rates for input, cached input and output.
[abstract.inc](https://abstract.inc) shows each model's output rate.
The rates are fixed when you submit the request.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.