# Troubleshooting

Ordered by how often it actually happens.

## 404 on every Anthropic call

Your Anthropic `baseURL` includes `/v1`. Remove it.

```
WRONG  baseURL: "https://www.spendline.ai/v1"   → requests /v1/v1/messages
RIGHT  baseURL: "https://www.spendline.ai"
```

The OpenAI SDK **does** need `/v1`. Both are correct. See
[anthropic](https://www.spendline.ai/agents/anthropic.md).

## 401 Unauthorized

| Cause | Fix |
|---|---|
| `SPENDLINE_API_KEY` unset or stale | Fresh key from Dashboard → API Keys |
| Key sent in the wrong header | Must be `x-spendline-key` |
| Key in a query string | Rejected outright. Use the header. |

The error body says which: *"No Spendline auth key was provided on this proxy
request. Include x-spendline-key."* means the header never arrived, usually the
framework dropped `default_headers`.

## Anthropic rejects the key / "invalid x-api-key"

You put the **Spendline** key in `x-api-key`. Anthropic owns that header for the
Claude provider key, so your Spendline key was forwarded upstream and rejected.

Rotate the Spendline key, it has now been sent to a third party, then use
`x-spendline-key`.

## 400 missing_required_attribution

```json
{"error_type":"missing_required_attribution","missing_fields":["customer_id"]}
```

Attribution enforcement is on. Send `x-agent-id`, `x-customer-id`, and
`x-spendline-tags` with a cost-centre key (`cost_center` / `costCenter` /
`department` / `team`).

A tags header without a cost-centre key fails even though tags were sent.

## 402 Payment Required

A budget blocked the call **before** any spend. This is correct behaviour, not a
bug.

- Do **not** retry against the provider.
- Do **not** raise the budget to make it pass.
- Surface it, and check `GET /api/budgets/summary/all` for which scope bound.

If the block is wrong, the path is an override request
(`POST /api/budgets/overrides`) so an owner approves it on the record.

## 403 Forbidden

Two different causes, read the body:

| Body | Meaning |
|---|---|
| policy / governance block | A model-block or token-cap policy refused the call. Check `GET /api/policies/blocked-events`. |
| `permission_denied` with `required` | Your credential lacks authority. A **child key** cannot create budgets/policies/reroutes. |

For `permission_denied`, do not attempt to escalate. Propose the change to the
human. See [onboarding](https://www.spendline.ai/agents/onboarding.md).

## Calls succeed but nothing shows in the dashboard

Almost always: the traffic is not going through Spendline at all. Check that the
base URL you edited is the one the running process uses, a second client
constructed elsewhere is the usual culprit. Re-run the Phase 1 audit and grep for
`api.openai.com` / `api.anthropic.com` again.

## Calls recorded, but customer_id is null or identical everywhere

`x-customer-id` is pinned in `default_headers` instead of passed per request.

```python
# WRONG
client = OpenAI(default_headers={"x-customer-id": "cus_123"})
# RIGHT
client.chat.completions.create(..., extra_headers={"x-customer-id": request.customer_id})
```

Verify with `GET /api/budgets/scopes`: if `customers` has one entry after calls
for several customers, it is pinned.

## Streaming hangs or returns nothing

- `/v1/responses` does **not** support streaming. `stream: true` is rejected. Use
  `/v1/chat/completions` if you need a stream from OpenAI.
- Check your client is not buffering the whole response.

## "Cross-provider reroute requires an active <provider> Provider Key"

```json
{"error_type":"missing_provider_key","provider":"anthropic"}
```

The account has no stored provider key for the reroute target. Storing one is
**human-only**: ask the human to add it in the dashboard. Never accept a provider
key into your context.

## Request entity too large

Only `/v1` accepts large bodies (40 MB, for base64 documents and vision
payloads). The management API under `/api/` has a small cap on purpose. You are
posting a large payload to the wrong path.

## 429 Too Many Requests

Rate limited. Back off and retry with exponential backoff.

## Model name rejected / unexpected provider

Resolve routing at runtime rather than guessing:

```bash
curl -s https://www.spendline.ai/agents/capabilities.json
```

Remember Together / Fireworks / Groq share open-weight model names, and the
account's stored provider key decides the actual host.

## Still stuck, diagnostic block

Do not loop. Produce this for the human and stop:

```
Spendline diagnostic
  Call site:        <file:line>
  SDK + version:    <e.g. openai-python 1.109.1>
  Base URL used:    <exact string from the code>
  Endpoint hit:     <full URL>
  Request headers:  <all key values REDACTED to ac_…1234>
  Model:            <name>
  Response status:  <code>
  Response body:    <full body>
  Phase 1 inventory table
```

Email to **fida@spendline.ai**. Redact every credential, a diagnostic block with
a live key in it is an incident.
