# Spendline quickstart (for coding agents)

Four steps. Do them in order. Nothing here requires a Spendline SDK.

## Prerequisites

- A Spendline API key in the environment as `SPENDLINE_API_KEY`.
  If the user has no account, see
  [onboarding](https://www.spendline.ai/agents/onboarding.md): do not invent a key
  and do not create an account with a password you chose.
- The application keeps its **own** provider API keys. Spendline forwards them;
  it does not replace them.

## Step 1. Repoint the base URL

| SDK | Set base URL to | Note |
|---|---|---|
| OpenAI (and any OpenAI-compatible client) | `https://www.spendline.ai/v1` | **With** `/v1` |
| Anthropic | `https://www.spendline.ai` | **Without** `/v1` |

That asymmetry is real. The OpenAI SDK appends `/chat/completions`; the
Anthropic SDK appends `/v1/messages`. Do not "fix" one to match the other.

## Step 2. Add attribution headers

Send these on every routed call:

```
x-spendline-key:   $SPENDLINE_API_KEY
x-agent-id:        support-bot          # stable per agent/feature
x-customer-id:     cus_123              # PER REQUEST: the billable customer
x-spendline-tags:  {"team":"support"}   # must carry a cost-centre key
```

`x-spendline-tags` must include one of `cost_center`, `costCenter`,
`department`, `team`.

Never put the Spendline key in `x-api-key`: Anthropic uses that header for the
Claude provider key, so it would be forwarded to Anthropic and rejected.

## Step 3. Working examples

**Python, OpenAI SDK**

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://www.spendline.ai/v1",
    api_key=os.environ["OPENAI_API_KEY"],          # your own provider key
    default_headers={
        "x-spendline-key": os.environ["SPENDLINE_API_KEY"],
        "x-agent-id": "support-bot",
        "x-spendline-tags": '{"team":"support"}',
    },
)

resp = client.chat.completions.create(
    model="gpt-5.6",
    messages=[{"role": "user", "content": "hello"}],
    extra_headers={"x-customer-id": "cus_123"},     # per request
)
```

**TypeScript, Anthropic SDK**

```ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://www.spendline.ai",              // no /v1
  apiKey: process.env.ANTHROPIC_API_KEY!,           // your own provider key
  defaultHeaders: {
    "x-spendline-key": process.env.SPENDLINE_API_KEY!,
    "x-agent-id": "support-bot",
    "x-spendline-tags": '{"team":"support"}',
  },
});

await client.messages.create(
  { model: "claude-sonnet-5", max_tokens: 256, messages: [{ role: "user", content: "hello" }] },
  { headers: { "x-customer-id": "cus_123" } },       // per request
);
```

**curl**

```bash
curl https://www.spendline.ai/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "x-spendline-key: $SPENDLINE_API_KEY" \
  -H "x-agent-id: smoke-test" \
  -H "x-customer-id: cus_123" \
  -H 'x-spendline-tags: {"team":"platform"}' \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"hello"}]}'
```

## Step 4. Handle the control responses

| Status | Meaning | What your code must do |
|---|---|---|
| 402 | A budget blocked the call before any spend | Surface the error. **Never** retry against the provider. |
| 403 | A policy blocked the call | Surface the error. **Never** route around it. |
| 401 | Bad Spendline key | Fix the key. Do not fall back. |
| 400 `missing_required_attribution` | A required header is missing | Add the attribution headers. |
| 429 | Rate limited | Back off and retry with exponential backoff. Do not fall back to the provider. |
| 5xx / timeout / network error | Spendline is unreachable | Retry **once** directly against the provider so the app stays up. |

Falling back on 402/403 defeats the entire product. Falling back on 5xx is
correct availability engineering. That distinction matters.

## Then verify

Follow [verification](https://www.spendline.ai/agents/verification.md).

## Do not route these through Spendline

Embeddings, image generation, audio/speech, vector search, and Google's native
`generateContent` shape. Leave them pointed at the provider.
