# Collection and optional API boundary

The optbrew audit command is `node skills/optbrew/scripts/audit.mjs --context <run-context.json> --modules naver-searchadvisor,magnus-seo --output-dir <run-dir>`. It writes raw responses, one hashed `<sha256>.response.json` record per response (final URL, redirect chain and every collected header line, repeated lines kept separate), one hashed `<sha256>.failure.json` record per failed fetch (answered redirect hops and whether one more attempt got no response), `collection.json`, `modules.json`, `observations.json` and `evidence-index.json` under `<run-dir>/evidence/`. The evidence index binds the run ID, context reference, raw response hashes, response record hashes (`responses`), failure record hashes (`failures`) and module/observation paths for later scoring and reporting. Reusing an output directory with an existing collection index is rejected. The audit never posts to a search engine or changes the site.

`RunContext.scope.allowed` is a nonempty list of `{ "origin": "https://example.org", "path_prefix": "/owned/" }`. An origin fixes scheme, host and effective port. The path prefix is segment bounded. `scope.local_targets` may list explicit `{ "host": "localhost", "port": 8765 }` pairs for a declared local target. Without this pair, loopback, private, link-local, ULA and mapped IPv4 addresses are blocked. The default ceilings are 50 URLs, depth 3, concurrency 2, 20 seconds, 5 MiB and 10 redirects. `max_depth` and `concurrency` may be set lower. `max_urls` defaults to 50 and the start questions may raise it to 200 (an audit of a site with more than 50 pages asks "sample or raise the limit"); a larger value is refused. The request limit (`max_urls`, 50 by default, at most 200) is a hard ceiling for pages and auxiliary resources together: every request, each redirect hop included, takes one slot immediately before it is sent, and the total never exceeds the limit. A redirect chain the limit stops leaves its URL as a `request_limit` unknown, and the hops it already received stay recorded. A failed fetch counts the hops it received plus at most one attempt that got no response; both are written to a hashed `<sha256>.failure.json` record so report replay can prove the count. The 20-second deadline covers DNS, redirects, connection and response together. Every redirect is checked against scope and fresh DNS; the Node transport connects to the vetted address and preserves TLS hostname verification. A host MCP or bulk crawler is selected only if it can guarantee the same boundary and cost controls.

The render stage is separate from collection and optional. `Protocol.render` fixes `{ enabled, pages, max_render_requests }`: only the listed pages (the first URL of each page type, at most 10) are rendered, each in its own headless Chrome context with a temporary profile, and a render makes at most 300 subrequests of its own; they do not count against the crawl's request limit. Every request of the page, its iframes, popups and workers passes the same scope rules before it is sent: an in-scope GET continues, a non-GET request or an out-of-scope document, XHR or fetch is refused, and an out-of-scope script, stylesheet, font or image GET is allowed only on a public host and is listed as third-party static. In direct mode Chrome resolves no name by itself: it is pinned to the addresses address vetting accepted (`--host-resolver-rules` with one `MAP` per allowed host, a host that cannot be vetted mapped to `^NOTFOUND`, and a final catch-all `MAP * ^NOTFOUND`), so the private-address block holds for the render too, and the record says `address_vetting: "pinned"`. A third-party static GET is fetched by Node with the collector's vetted fetch (private, loopback, link-local and metadata addresses are refused, the collection byte limit and deadline apply, at most 4 such fetches run at once, and no redirect is followed: one intercepted request is one fetch) and handed to Chrome with its content type and its `Access-Control-Allow-Origin`, `Access-Control-Allow-Credentials` and `Timing-Allow-Origin` headers, never a cookie; a refusal, a redirect or another failure is recorded as `third_party_refused`, `third_party_redirect` or `third_party_failed`. Each request record carries the `status` Chrome saw or its `error` text, and `render_ledger` entries are `response`, `error` or `skipped` accordingly. Behind an authenticating proxy Chrome gets the proxy and answers only proxy challenges with the credential, which stays in memory; the record says `"proxy"` and no record names more than host and port. The final DOM is stored as `<sha256>.render-dom` raw evidence (the HTML bytes), the page record (`url`, `final_url`, `status`, `dom_ref`, `dom_sha256`, `title`, same-origin `links`, `console_errors`, `requests`, `blocked_reason`, `address_vetting`) as `<sha256>.render.json`, and the index lists them under `renders`; every request goes into `render_ledger` in the entry shape of `ledger.json` (plus the `page` it belongs to). Without a Chrome nothing is rendered. Known limit: a page can still open a WebSocket or preconnect to a host the rules would refuse, because those do not pass the request interception.

The direct Anthropic Messages adapter accepts only text-only `{model,max_tokens,messages}` requests. Each message has only `{role,content}` with string content. It accepts these exact model IDs: `claude-fable-5-1`, `claude-opus-5-5`, `claude-sonnet-5-5`, `claude-haiku-5-5`. Anthropic's [model overview](https://platform.claude.com/docs/en/models/overview), checked 2026-10-14, lists a 1,000,000-token context window and 128,000 maximum output tokens for each. Every attempt must reserve at least `1,000,000 + request.max_tokens` in the `tokens` unit. The adapter requires exactly one token upper bound, a matching ledger budget and approval reference, and an `ANTHROPIC_API_KEY` environment variable. It rejects unknown models, extra fields, tools, web search, non-text input, currency-only bounds and smaller token reservations before any provider request. The official [token-counting guide](https://platform.claude.com/docs/en/build-with-claude/token-counting), checked 2026-10-14, calls count results estimates that may differ from billed input usage; they are not accepted as hard upper bounds.

The reserved worst-case token bound is not an estimate of actual billing. Record estimated spend, its price source/date and the user-approved stop condition separately in the cost decision. `CostLedger` keeps credits, tokens, search calls and minor currency units separate; `currency_minor` requires a currency code. This direct adapter enforces only its verified token boundary. Tools, paid search and other API shapes remain unavailable in direct mode until their hard bounds are verified. They may be routed through separately approved host capabilities or other provider data with `cost_enforcement: host_policy` explicitly reported when code cannot lock spending. Optbrew never substitutes a model or silently turns search off. The direct adapter tries at most twice for retryable HTTP errors. A timeout, malformed response or unmeasurable charge settles as `unknown`, retaining the reservation. Provider-reported usage above the reservation is stored at its observed amount and blocks subsequent spending. Successful responses are redacted and stored beside the ledger under `evidence/provider/`.

Primary syntax checked 2026-10-14: [Anthropic Messages API](https://docs.anthropic.com/en/api/messages), [Anthropic web search tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool), [Node HTTPS request](https://nodejs.org/api/https.html#httpsrequesturl-options-callback), [Node DNS lookup](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback). These describe API/CLI mechanics, not a price approval or evidence of an actual paid call.

## Getting an Anthropic API key

An Anthropic Console API key is separate from a Claude web or Claude Code subscription. Follow the official [API key guide](https://platform.claude.com/docs/en/get-api-key): open the Console, go to **Settings → API keys**, and create a key with the intended scope. Put it in the local `ANTHROPIC_API_KEY` environment variable. These examples contain placeholders only; replace the placeholder in your own shell, never in a report, issue, or shared command log.

```bash
export ANTHROPIC_API_KEY="<YOUR_ANTHROPIC_API_KEY>"
```

```powershell
$env:ANTHROPIC_API_KEY = '<YOUR_ANTHROPIC_API_KEY>'
```

The current key guide notes that a key spanning multiple Anthropic workspaces can require `anthropic-workspace-id`. The direct adapter sends `x-api-key`, `anthropic-version`, and JSON content headers; it does **not** set a workspace ID header. Do not promise this direct path for a key requiring that header. Use a compatible key or a separately approved host capability. The env-var examples configure a shell only; they do not initiate a paid request or confirm account access.

Before the first paid attempt, record a current pricing source/date, estimated spend, a user-approved limit or stop condition, and the separate token reservation described above. Unknown price is not zero price. A host subscription is not an API allowance. The ledger's reserved token capacity is intentionally conservative and differs from provider-billed usage. Provider timeout or uncertain usage remains `unknown`; reported usage over a reservation remains an overrun and blocks further spending.

## SE Ranking is a separate capability

SE Ranking has a separate [API Dashboard and account plan](https://seranking.com/api.html). Its [MCP guide](https://seranking.com/api/integrations/mcp) documents a Streamable HTTP endpoint at `https://api.seranking.com/mcp`, OAuth 2.1 and an explicit `X-Api-Key` option where supported. Available tools and credits depend on the subscribed account. An Anthropic key neither authenticates SE Ranking nor pays its credits. A connected host capability must disclose its cost enforcement and tool access before use; the direct Anthropic token guard does not cover SE Ranking.

The installed Codex CLI's `codex mcp add --help` accepts `NAME --url URL` for Streamable HTTP and an optional bearer-token env var. It does not expose a generic header option in the checked version. For the SE Ranking OAuth route, registration syntax is:

```sh
codex mcp add se-ranking --url https://api.seranking.com/mcp
```

The installed Claude Code CLI's `claude mcp add --help` accepts `--transport http`, `--scope local|user|project`, and `--header` for explicit headers. Its OAuth registration syntax is:

```sh
claude mcp add --transport http --scope local se-ranking https://api.seranking.com/mcp
```

These commands change host MCP configuration and may start OAuth. They are reference syntax only; this repository's local tests check help output and do not register a server or authenticate an account. Use the host's current help and the SE Ranking guide before connecting. Never place a real `X-Api-Key` value in a committed config, command transcript, or report. The former `rmcp` setup shown in some older SE Ranking material is not the checked Codex CLI syntax.
