# Quickstart

> Create a Mortar API key and send your first chat completion with the OpenAI Python SDK, the OpenAI TypeScript SDK or curl, then stream the reply as it is generated.

Mortar speaks the OpenAI chat-completions API, so you can use the official OpenAI SDKs pointed at Mortar. In a few minutes you'll create an API key, send one request and stream a reply.

## Create an API key

1. [Sign in](/sign-in?returnTo=%2Fsettings%2Fkeys) and open **API keys** in your settings.
2. Create a key and give it a name you'll recognise later, such as `quickstart`. It expires in 30 days unless you choose otherwise.
3. Copy the key. It starts with `mk_live_`, and Mortar shows it only once.

Creating keys needs the Admin or Owner role in your organization. [Authentication](/docs/authentication) explains how keys are stored, how they expire, and how to rotate and revoke them.

Keep the key out of your code. Export it in the shell you'll run the examples from:

```bash title="Terminal"
export MORTAR_API_KEY="mk_live_your_key_here"
```

## Install an SDK

Skip this step if you'll use curl.

```bash tab="Python" group="Install an SDK"
pip install openai
```

```bash tab="TypeScript"
npm install openai
```

## Send a request

Every example sends the same request: one user message to `Qwen/Qwen2.5-7B-Instruct`, the model this deployment serves. The base URL is `https://api.mortar.one/v1`.

```python tab="Python" group="Send a request"
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://api.mortar.one/v1",
    api_key=os.environ["MORTAR_API_KEY"],
)

completion = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "Explain prefix caching in one sentence."}],
    stream=False,
)
print(completion.choices[0].message.content)
```

```typescript tab="TypeScript"
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.mortar.one/v1",
  apiKey: process.env.MORTAR_API_KEY,
});

const completion = await client.chat.completions.create({
  model: "Qwen/Qwen2.5-7B-Instruct",
  messages: [{ role: "user", content: "Explain prefix caching in one sentence." }],
  stream: false,
});
console.log(completion.choices[0].message.content);
```

```bash tab="curl"
curl https://api.mortar.one/v1/chat/completions \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "Explain prefix caching in one sentence."}],
    "stream": false
  }'
```

> [!IMPORTANT]
> Pass `stream` explicitly. When a request leaves it out, Mortar streams the reply, and the OpenAI SDKs leave it out unless you set it. Without `stream=False` (Python) or `stream: false` (TypeScript), the SDK receives an event stream where it expects JSON.

The reply is a chat completion object. The answer is in `choices[0].message.content`, and `usage` counts the tokens you're billed for:

```json title="Response" output
{
  "id": "chatcmpl-5d0e8f6a2b4c4e1f9a3b7c6d",
  "object": "chat.completion",
  "created": 1767225600,
  "model": "Qwen/Qwen2.5-7B-Instruct",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Prefix caching keeps the attention state for a prompt's opening tokens, so a request that starts like an earlier one skips recomputing them."
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 19,
    "completion_tokens": 29,
    "total_tokens": 48
  }
}
```

## Stream the reply

Set `stream` to true and Mortar sends the reply as server-sent events while it is generated, so you can show the first words before the last are written.

```python tab="Python" group="Stream the reply"
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://api.mortar.one/v1",
    api_key=os.environ["MORTAR_API_KEY"],
)

stream = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "Explain prefix caching in one sentence."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta if chunk.choices else None
    if delta and delta.content:
        print(delta.content, end="", flush=True)
print()
```

```typescript tab="TypeScript"
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.mortar.one/v1",
  apiKey: process.env.MORTAR_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "Qwen/Qwen2.5-7B-Instruct",
  messages: [{ role: "user", content: "Explain prefix caching in one sentence." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
process.stdout.write("\n");
```

```bash tab="curl"
curl -N https://api.mortar.one/v1/chat/completions \
  -H "Authorization: Bearer $MORTAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "Explain prefix caching in one sentence."}],
    "stream": true
  }'
```

With curl you see the raw stream: one `chat.completion.chunk` per event, ending with `[DONE]`. The SDKs parse these frames for you.

```text title="Response" output wrap
data: {"id":"chatcmpl-5d0e8f6a2b4c4e1f9a3b7c6d","object":"chat.completion.chunk","created":1767225600,"model":"Qwen/Qwen2.5-7B-Instruct","choices":[{"index":0,"delta":{"role":"assistant"},"logprobs":null,"finish_reason":null}]}

data: {"id":"chatcmpl-5d0e8f6a2b4c4e1f9a3b7c6d","object":"chat.completion.chunk","created":1767225600,"model":"Qwen/Qwen2.5-7B-Instruct","choices":[{"index":0,"delta":{"content":"Prefix caching keeps"},"logprobs":null,"finish_reason":null}]}

data: {"id":"chatcmpl-5d0e8f6a2b4c4e1f9a3b7c6d","object":"chat.completion.chunk","created":1767225600,"model":"Qwen/Qwen2.5-7B-Instruct","choices":[{"index":0,"logprobs":null,"finish_reason":"stop"}]}

data: [DONE]
```

[Stream responses](/docs/guides/streaming) covers the frame format in full, including usage reporting, cancellation and errors that arrive mid-stream.

## Next steps

- Make repeated requests cheaper and faster with [cache-friendly prompts](/docs/guides/prompt-caching).
- Look up every field and error in the [chat completions reference](/docs/api/chat-completions).
- Compare prices and context windows in the [model catalog](/models).
