# Superposition Adapter API — Developer Guide

> Paste this whole document into an AI coding assistant (Claude, ChatGPT, etc.)
> and ask it to help you integrate. It is self-contained.

**What it is:** a stateless, OpenAI Chat Completions-compatible inference API served on
your own **dedicated GPU capacity**. Point the standard OpenAI SDK at our base URL, use
your API key, and pass your **Endpoint ID** as the `model`.

Two things differ from a hosted model like GPT-4:

1. You control **when the model is active** — activating loads it onto a dedicated GPU
   (real cost), so you turn it on and off.
2. The "model" is your own **trained adapter**, pinned to a specific version.

**Base URL:** `https://api.thesuperposition.ai/v1` (the SDK appends `/chat/completions`).

You create the API key and endpoint in the Superposition dashboard (Developer section);
this guide covers using them from code.

---

## 1. Concepts

| Term | Meaning |
|------|---------|
| **Endpoint** | The addressable model you call. It pins one specific trained **adapter version** and is named by an **endpoint id**, which you pass as the OpenAI `model` field. |
| **API key** | Your secret (`tsk_…`). Authenticates every inference and activation call. Shown once when created; store it securely. Sent as a Bearer token. |
| **Active / live** | An endpoint must be **live** (loaded on a dedicated GPU and serving) before it answers. Activating it takes ~1–3 min to reach live (startup). You control activation explicitly. |
| **Stateless** | No conversation memory is kept. Send the full message history on every call, exactly like the OpenAI API. |

---

## 2. Authentication

Every request carries your API key as a Bearer token:

```
Authorization: Bearer tsk_your_key_here
```

(An `X-API-Key: tsk_…` header is also accepted.) A missing, invalid, expired, or revoked
key returns `401`:

```json
{ "error": { "type": "invalid_request_error", "code": "invalid_api_key", "message": "Invalid or missing API key." } }
```

---

## 3. The request lifecycle

```
1. Activate    POST /v1/endpoints/{id}/activate          → GPU starts loading
2. Wait live   GET  /v1/endpoints/{id}/status  (poll)    → until { "live": true }
3. Infer       POST /v1/chat/completions                 → completions (stream or not)
4. (optional)  POST /v1/endpoints/{id}/deactivate        → release the GPU
```

**Inference never activates an endpoint.** A call to an inactive endpoint returns
`409 endpoint_not_live` and does nothing else — activation is always the separate step 1.
Design your app to activate *ahead* of traffic, not to trigger activation by sending a chat call.

Minimal end-to-end example:

```python
import time, requests
from openai import OpenAI

BASE_URL = "https://api.thesuperposition.ai/v1"
API_KEY  = "tsk_your_key_here"
ENDPOINT = "your_endpoint_id"
auth = {"Authorization": f"Bearer {API_KEY}"}

# 1. Activate (optionally ?ttlMinutes=60 to auto-deactivate after an hour)
requests.post(f"{BASE_URL}/endpoints/{ENDPOINT}/activate", headers=auth)

# 2. Poll until live (startup ~1–3 min; poll no faster than ~5s)
while not requests.get(f"{BASE_URL}/endpoints/{ENDPOINT}/status", headers=auth).json()["live"]:
    time.sleep(5)

# 3. Infer with the standard OpenAI SDK
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
resp = client.chat.completions.create(
    model=ENDPOINT,
    messages=[{"role": "user", "content": "Introduce yourself."}],
)
print(resp.choices[0].message.content)

# 4. Deactivate to release the GPU when done
requests.post(f"{BASE_URL}/endpoints/{ENDPOINT}/deactivate", headers=auth)
```

---

## 4. Managing activation

All activation calls use your API key. There are three ways an endpoint becomes active.

### 4.1 Manual — active until you stop it

```
POST /v1/endpoints/{id}/activate
→ 200 { "status": "warming" }   // or "ready" if it was already live
```

Stays active until you `deactivate`, bounded by a **24h safety cap** (re-activate to extend).

### 4.2 TTL — active for a fixed duration

```
POST /v1/endpoints/{id}/activate?ttlMinutes=60
→ 200 { "status": "warming" }
```

Auto-deactivates after `ttlMinutes` (clamped 1–1440). No need to remember to deactivate.

### 4.3 Scheduled — recurring windows

Scheduled activation (e.g. "active 8–11am Mon–Fri") is **configured by the account owner in
the dashboard**, not through this API. The server activates a few minutes before each window
opens and deactivates at the end — no per-window call from your app. Your app just checks
`status` and infers.

### 4.4 Deactivate

```
POST /v1/endpoints/{id}/deactivate
→ 200 { "status": "deactivated" }
```

### 4.5 Status (poll this)

```
GET /v1/endpoints/{id}/status
→ 200 {
    "active": true,                        // activation is turned on
    "live": true,                          // ready to serve inference RIGHT NOW
    "warm_until": "2026-07-16T20:00:00Z"   // when activation expires (user-facing)
  }
```

- Gate your inference on **`live`**. After `activate`, poll until `live: true`
  (startup ~1–3 min), then send chat calls.
- **Poll no faster than ~5s** — readiness refreshes on a ~20s server cadence, so tighter
  polling only adds load.
- Show `warm_until` as the human "active until" time. (`ready_until` may also appear; it's
  an internal lease horizon — ignore it.)

### 4.6 Activation error responses (on `activate`)

| HTTP | `type` | Meaning |
|------|--------|---------|
| `429` | `capacity_exceeded` | Activating would exceed your plan's dedicated-GPU ceiling. |
| `409` | `no_dedicated_capacity` | No dedicated serving capacity available for this adapter's base model. |
| `409` | `adapter_not_ready` | The pinned adapter isn't in a Ready state. |
| `404` | — | Endpoint not found / not yours. |

---

## 5. Chat completions — non-streaming

Point the OpenAI SDK at the base URL; use the **endpoint id** as `model`.

> **What goes in `model`?** Your **Endpoint ID** — the copyable id shown in the Endpoints
> table under **Developer** in your dashboard (a value like
> `df610c4483b04f9aa960f51624128796`). It is *not* the base model name (e.g. Llama), *not*
> your world or character name, and carries no version number — the endpoint is already
> pinned to one specific trained version.

**Python**

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.thesuperposition.ai/v1", api_key="tsk_your_key_here")

resp = client.chat.completions.create(
    model="df610c4483b04f9aa960f51624128796",   # your endpoint id
    messages=[
        {"role": "system", "content": "You are concise."},
        {"role": "user", "content": "Introduce yourself."},
    ],
    temperature=0.4,
    max_tokens=256,
)
print(resp.choices[0].message.content)
```

**curl**

```bash
curl https://api.thesuperposition.ai/v1/chat/completions \
  -H "Authorization: Bearer tsk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "df610c4483b04f9aa960f51624128796",
    "messages": [{"role": "user", "content": "Introduce yourself."}]
  }'
```

**Response** (`chat.completion`)

```json
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1752696000,
  "model": "df610c4483b04f9aa960f51624128796",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "…" }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 12, "completion_tokens": 40, "total_tokens": 52 }
}
```

> `usage` is an estimate (~4 chars/token) in v1, not an exact token count.

**Alternate path form.** You may also target the endpoint in the URL and omit `model`:
`POST /v1/endpoints/{id}/chat/completions`. The `model`-field form is preferred (it's what
stock SDKs send).

---

## 6. Chat completions — streaming

Set `stream: true`. The response is Server-Sent Events (`text/event-stream`): a sequence of
`data: {chunk}` lines terminated by `data: [DONE]`.

**Python**

```python
stream = client.chat.completions.create(
    model="df610c4483b04f9aa960f51624128796",
    messages=[{"role": "user", "content": "Tell me a short story."}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end="", flush=True)
```

**Wire format** — each chunk is a `chat.completion.chunk`:

```
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Once"},"finish_reason":null}]}

…

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]
```

The first chunk carries `delta.role = "assistant"`; content chunks carry `delta.content`;
the final chunk has an empty delta with `finish_reason: "stop"`, then `[DONE]`. This matches
OpenAI's stream shape, so any OpenAI-compatible streaming client works unchanged.

---

## 7. Supported request parameters

| Field | Notes |
|-------|-------|
| `model` | **Required.** Your endpoint id. |
| `messages` | **Required.** Array of `{ role, content }`; roles `system` / `user` / `assistant`. A `system` message, if used, must be the **first** entry (see below). |
| `temperature` | Optional. |
| `max_tokens` | Optional. |
| `top_p` | Optional. |
| `stop` | Optional. String or array of strings. |
| `stream` | Optional, default `false`. |

Unsupported OpenAI params (`tools`/function calling, `n > 1`, `logprobs`, `response_format`,
image/audio content, …) are **ignored**, not errored. The adapter serves its own trained
persona; you don't need to supply a system prompt to get its voice (though you may add one).

> **A `system` message must be the first message.** If you send a `system` message, it has
> to be the very first entry in `messages`. A `system` message in any other position causes
> the whole request to fail (`400 invalid_request_error`) — so there's effectively at most
> one, and it's always first. A system message is entirely optional: put your instructions in
> that leading `system` message, or just include them in your `user` / `assistant` messages
> instead.

---

## 8. Inference error responses

OpenAI-shaped `{ "error": { "type", "code", "message" } }`.

| HTTP | `type` / `code` | Meaning & what to do |
|------|-----------------|----------------------|
| `401` | `invalid_api_key` | Bad/missing key. |
| `404` | `model_not_found` | Endpoint id unknown or not yours. |
| `409` | `endpoint_not_live` | Endpoint is inactive. Activate it (§4) and poll `status` until `live` before retrying. |
| `400` | `invalid_request_error` | Missing `messages`/`model`, empty body, or a `system` message that isn't the first message. Fix the request. |
| `502` | `server_error` | Upstream model error. Retry with backoff. |

---

## 9. Limits & behavior (v1)

- **Text only.** No audio/image/voice.
- **Stateless.** Send the full message history each call; nothing is stored between calls.
- **Activate before you call.** Inactive endpoints return `409 endpoint_not_live`; inference
  never triggers activation.
- **Dedicated capacity.** Each active endpoint runs on a GPU reserved for you — predictable
  latency, but you're billed for active time, so deactivate (or use TTL / scheduled) when idle.
- **Pinned version.** An endpoint serves the exact adapter version pinned to it; retraining
  the adapter does not change what the endpoint serves until the owner promotes the new
  version.

---

## 10. Getting a key and endpoint

API keys and endpoints are created by the account owner in the Superposition dashboard,
under **Developer**:

1. **Enable API access** (creates your API tenant).
2. **Create an API key** — copy the secret once and store it securely.
3. **Create an endpoint** pinning a trained model — its **endpoint id** is what you pass as
   `model`.

Then follow §3 to activate it and make your first call.
