# 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
import uuid
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
        "Idempotency-Key": str(uuid.uuid4()),        # per logical 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
      "Idempotency-Key": crypto.randomUUID(),        // per logical 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 "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"hello"}]}'
```

**`Idempotency-Key` is required** on every proxied request to the hosted
service (a request without one is refused with `400 idempotency_key_required`
before anything is sent to the provider). Generate one value (a UUID) per
logical request **before the first attempt** and reuse it on every retry of
that request. The SDK's own automatic retries reuse the headers of the call, so
generating it in the call, as below, is correct. If you write your own retry
loop, create the key outside the loop: a fresh key per attempt makes each retry
a separate purchase.

## 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. |
| 400 `idempotency_key_required` | No `Idempotency-Key` was sent | Add one per logical request, reused on retries. |
| 409 `duplicate_request` | That `Idempotency-Key` already completed | Do not resend under a new key; the work was already done and recorded. |
| 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. A request sent
directly to the provider is outside Spendline's budget checks and cost records,
so a fallback call is neither controlled nor attributed.

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