# Connect flow: analyse, propose, wait for the human, install, verify

You were sent here from the **Connect Spendline** page in a human's dashboard.
They chose you (Cursor, Claude Code, or another coding agent) to set Spendline
up for them. The promise that page makes is that they will **not** read API
documentation, edit code by hand, learn Spendline's headers, or decide up front
how spend should be attributed. You do the technical work in their existing
development environment; Spendline shows them your recommendation; they only
confirm.

Work through the steps **in order**. Step 3 blocks on a human click. Do not
skip it, and do not change a line of application code before it clears.

Base URL: `https://www.spendline.ai` unless the human gave you a pilot URL.
Below, `<SPENDLINE_URL>` means that URL.

## Step 0. Confirm this is the hosted proxy

Read Phase 0 of <https://www.spendline.ai/integrate.md> first. If the human has
said "in-network", "on-premise", "prompts must not leave our infrastructure", or
handed you a `.tgz` or a `SPENDLINE_KEY` (not `SPENDLINE_API_KEY`), this is the
wrong flow: **stop and tell them**. The in-network deployment is not installed
from here, and in it Spendline never sees the prompts. Do not improvise a hybrid.

## Step 1. Credential, without seeing it

You need `SPENDLINE_API_KEY` in the application's `.env`. Check whether it is
already there. If it is, move on: you never need to read its value.

If it is not:

- Tell the human the Connect page has a **Create installation key** button that
  copies a ready `.env` line to their clipboard. Ask them to paste it into
  `.env` and tell you when done.
- Or start the device flow in <https://www.spendline.ai/agents/onboarding.md>:
  you request access, they approve in their browser, and a scoped key is issued
  to you exactly once. Write it straight to `.env`.

Never ask the human to paste a key into chat. If they do anyway, tell them to
rotate it in Dashboard → API Keys. Confirm `.env` is in `.gitignore`.

Everything below authenticates with the header `x-spendline-key: $SPENDLINE_API_KEY`.

## Step 2. Analyse the repository, then propose

The analysis happens **in the human's development environment**, on their
machine or their existing coding-agent sandbox. Spendline never receives or
stores source code, prompts, or responses as part of this flow. What you send
in Step 2 is a handful of identifier names.

Find, and keep notes on:

- every place an AI provider is called: SDK constructors (`new OpenAI(`,
  `Anthropic(`, `AsyncOpenAI`, …), raw HTTP to a provider host, and wrappers
  such as LangChain, LlamaIndex, LiteLLM and the Vercel AI SDK. The full list
  of hosts and shapes is in <https://www.spendline.ai/integrate.md> Phase 1.
- which providers and SDKs those are. Spendline proxies the ten providers in
  <https://www.spendline.ai/agents/providers.md>; anything else stays as-is.
- the value that identifies the **customer** at each call site: a tenant,
  workspace, organisation, account or user id that is available on the request
  or job context. This is the one that must vary **per request**.
- the **agent**: a stable name for the bot, feature or service making the call
  (`support-agent`, `doc-summarizer`). Often the module or service name.
- the **workflow**: a route name, job type, ticket type or pipeline stage that
  groups a multi-step run.
- the **team / cost centre**: a team, department or product-area value if the
  codebase has one (config, ownership file, service metadata).
- the **environment**: usually `process.env.NODE_ENV`, `ENV`, `APP_ENV` or a
  deployment stage variable.

Then propose. One request, identifier names only:

```bash
curl -X POST <SPENDLINE_URL>/api/connect/plan \
  -H "x-spendline-key: $SPENDLINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_tool": "claude-code",
    "repository_label": "acme-api",
    "providers": ["openai", "anthropic"],
    "sdks": ["openai-node", "@anthropic-ai/sdk", "langchain"],
    "call_sites": 7,
    "mappings": {
      "customer":    { "source": "workspace.id",         "kind": "expression", "confidence": "high",   "why": "Every request handler has req.workspace; billing already keys on it." },
      "agent":       { "source": "ticket-triage",        "kind": "literal",    "confidence": "high",   "why": "One service, one name. Two features would be two agents." },
      "workflow":    { "source": "route.name",           "kind": "expression", "confidence": "medium", "why": "Routes are the unit your team already discusses cost in." },
      "team":        { "confidence": "unknown", "question": "Which team should AI spend from this service be charged to?" },
      "environment": { "source": "process.env.NODE_ENV", "kind": "expression", "confidence": "high" }
    }
  }'
```

Rules the server enforces, so read them before you send:

| Rule | Why |
|---|---|
| `agent_tool` is `cursor`, `claude-code` or `other`. | Shown to the human as "Cursor found…". |
| `providers` are keys from `/agents/providers.md`. An unknown one is refused with the valid list. | Never advertise a provider Spendline cannot proxy. |
| A mapping `source` is **one identifier-like expression** (`workspace.id`), or a fixed word value when it genuinely never changes (`support`). Max 120 characters, no newlines, braces or semicolons. | This channel carries names, not code. Anything else is refused. |
| Every confident mapping states `kind`: **`expression`** (read from the app at runtime, every call) or **`literal`** (a fixed value that never changes). Required — the server will not guess it, and refuses a `source` that does not match the `kind` you declared. | `"tenant"` is a valid word AND a valid identifier, so the string cannot say which it is. Installing an expression as a literal hardcodes one customer's id onto every call, which looks fine in the diff and is wrong in the ledger. |
| A mapping you cannot determine **confidently** has `"confidence": "unknown"`, **no** `source`, and a one-line business-language `question`. | **Never invent a mapping.** The page asks the human your question in plain words. |
| `customer`, `agent` and `team` are required keys (as confident or unknown). `workflow` and `environment` are optional. | These are what attribution enforcement requires on the wire. |

A `201` means the page just updated live. A `400` tells you exactly which field
to fix; fix it and resend. Sending again replaces your earlier proposal.

Tell the human, in one sentence, that your recommendation is waiting in the
Spendline tab. Do **not** paste the raw plan into chat as a wall of JSON.

## Step 3. Wait for the human's decision

```bash
curl <SPENDLINE_URL>/api/connect/plan -H "x-spendline-key: $SPENDLINE_API_KEY"
```

Every 5 seconds, for up to 15 minutes.

| `plan.status` | Meaning | What to do |
|---|---|---|
| `proposed` | Not decided yet | Keep polling. **Change no code.** |
| `confirmed` | The human clicked **Use this setup** or edited and confirmed | Install using `plan.decision.mappings`, which may differ from what you proposed. Respect their edits exactly. |
| `discarded` | The human started over | Stop. Ask how they want to proceed. |
| `plan: null` | Expired (24h) or never sent | 200, not an error. Re-run Step 2 if they still want to proceed. |

If the human answers one of your questions in the dashboard, the answer arrives
here as a confident mapping with the value they typed. Use it.

## Step 4. Install

Report progress first, then work:

```bash
curl -X POST <SPENDLINE_URL>/api/connect/plan/stage \
  -H "x-spendline-key: $SPENDLINE_API_KEY" -H "Content-Type: application/json" \
  -d '{"stage":"installing"}'
```

Then follow <https://www.spendline.ai/integrate.md> Phases 1 to 5 with these
constraints, which are the promises the Connect page made:

1. Audit every relevant provider call before editing anything.
2. Route only the supported shapes through Spendline (chat-completions,
   Anthropic messages, OpenAI Responses). Leave embeddings, image and audio
   generation and Google's native `generateContent` as they are.
3. Handle wrappers where they expose base URL and headers (LangChain, Vercel
   AI SDK, …). If a wrapper cannot carry per-request headers, say so in your
   report rather than forcing it.
4. Make the **smallest safe change**: prefer one shared client factory only if
   that refactor is small in this codebase.
5. Authenticate with `x-spendline-key` read from `SPENDLINE_API_KEY`. Put
   `SPENDLINE_URL` and a `SPENDLINE_ENABLED` flag in env config too.
6. Attribution values come from the **confirmed mappings**, and each mapping's
   `kind` says how to install it: `expression` is **evaluated at runtime on
   every request** (`x-customer-id` from `workspace.id` means reading
   `workspace.id` per call, never the value you saw once); `literal` is the
   fixed string itself. Send `x-customer-id`, `x-agent-id`, `x-workflow-id`
   where present, and `x-spendline-tags` as JSON carrying `team` and `env`.
   **Never hardcode a customer id** or any other `expression` mapping. The
   human may have edited both the source and its kind — respect what
   `decision.mappings` says, not what you proposed.
7. Preserve existing provider credentials and behaviour. Provider keys stay
   where they are; Spendline forwards them.
8. No secrets in source code. Env only.
9. Send an `Idempotency-Key` per logical request, created before the first
   attempt and reused on retries, as integrate.md Phase 4 describes.
10. Run the project's tests, type checks and build. Fix what you broke.

## Step 5. Verify, and confirm Spendline saw it

```bash
curl -X POST <SPENDLINE_URL>/api/connect/plan/stage \
  -H "x-spendline-key: $SPENDLINE_API_KEY" -H "Content-Type: application/json" \
  -d '{"stage":"verifying"}'
```

The page now reads **Waiting for your first AI call…**. Make one safe, cheap
real request through the integrated code path per provider (a short prompt,
a small `max_tokens`), with real attribution values, not placeholders like
`test-user`. Then check it landed:

```bash
curl "<SPENDLINE_URL>/api/calls?page=1" -H "x-spendline-key: $SPENDLINE_API_KEY"
```

The newest row should carry your model, `customer_id`, `agent_id`, and tags
with `team` and `env`. The page turns to **You're live ✓** on its own and shows
what Spendline detected; the human does not refresh anything.

If the call failed or attribution is incomplete, diagnose from
<https://www.spendline.ai/agents/troubleshooting.md> and fix it yourself:
a `401` is the key, a `400 missing_required_attribution` names the missing
field, a `402` is a deliberate budget block and must not be retried around.
Ask the human only when something genuinely cannot be determined safely, and
ask it as a business question, not a header name.

When the row is right:

```bash
curl -X POST <SPENDLINE_URL>/api/connect/plan/stage \
  -H "x-spendline-key: $SPENDLINE_API_KEY" -H "Content-Type: application/json" \
  -d '{"stage":"done"}'
```

## Step 6. Report

Finish with a short summary for the human: the call sites you routed, the ones
you left alone and why, the env vars you added, and the verification result per
provider. Keep the tone of the page: they chose you so they would not have to
learn this.
