Skip to content

feat(backend): attribute OpenRouter requests to the Platypus app - #427

Merged
willdady merged 1 commit into
mainfrom
feature/openrouter-app-attribution
Aug 8, 2026
Merged

feat(backend): attribute OpenRouter requests to the Platypus app#427
willdady merged 1 commit into
mainfrom
feature/openrouter-app-attribution

Conversation

@willdady

@willdady willdady commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Closes #426

What

OpenRouter credits each request to an app via HTTP-Referer, and that credit is what puts an app on its public model pages and rankings. Platypus sent nothing, so usage went untracked unless an operator hand-typed the headers into a Provider's Headers field.

OpenRouter Providers now send three attribution headers by default:

Header Value
HTTP-Referer https://github.com/willdady/platypus
X-OpenRouter-Title Platypus
X-OpenRouter-Categories personal-agent,general-chat

These are defaults, not policy. A Provider's own headers win, matched case-insensitively so a hand-written http-referer replaces the default rather than shipping a second referer. A blank referer in any casing is an explicit opt-out and drops all three, while leaving unrelated custom headers alone.

Header names and both category slugs were verified against OpenRouter's app-attribution docs.

Deliberately no environment variables

The issue proposed OPENROUTER_HTTP_REFERER / OPENROUTER_APP_TITLE / OPENROUTER_APP_CATEGORIES. Rejected during triage: every other per-Provider knob (API key, base URL, headers, extra body, region) lives on the Provider row, which is scoped to an Organization or Workspace, while backend env vars here are deploy-time infrastructure. The existing Headers field already provides override and opt-out — at finer granularity than env vars would. No schema change, no form change, no new config surface.

Notes for review

  • Default-on attribution is a policy call. Self-hosters' OpenRouter usage counts toward the Platypus project's public ranking unless they opt out. Nothing else changes — same endpoint, their own key and account, same cost. Worth a conscious sign-off; it also sits against ADR-0006's default-deny posture for reach-bearing config, though attribution headers add no destination and no credential.
  • Blank-referer opt-out checks every casing. Resolving a header name to one key would let a later HTTP-Referer hide an earlier blank http-referer and silently keep attribution on.
  • headers is jsonb, cast rather than parsed on read, so a value can be a non-string at runtime despite the type. A malformed row passes through as before instead of throwing on every model call.
  • Two non-blank casings of the same header both still ship. That is the operator's own data passing through as it did before this change; silently dropping one seemed worse.

Docs

Documents the Provider Headers field, which was previously undocumented — headers were covered for MCPs and Webhooks but not Providers — plus the attribution defaults and how to re-attribute or opt out.

Testing

  • pnpm test — 1513 backend tests pass, 8/8 tasks
  • pnpm typecheck and pnpm lint clean
  • Docs contract 24/24
  • New coverage: defaults, override, case-insensitive collision, passthrough of unrelated headers, opt-out (both casings, whitespace-only), non-string values, and that no other Provider type gained defaults

Not verified: that requests appear under the Platypus app in the OpenRouter dashboard. That needs a real account and actual spend — a manual post-merge check.

🤖 Generated with Claude Code

OpenRouter credits each request to an app via HTTP-Referer, which is what
puts an app on its public model pages and rankings. Nothing supplied those
headers, so Platypus usage went untracked unless an operator typed them into
a Provider's Headers field by hand.

Send HTTP-Referer, X-OpenRouter-Title and X-OpenRouter-Categories as defaults
beneath a Provider's own headers. The Provider wins on collision, matched
case-insensitively so a hand-written `http-referer` replaces the default
rather than shipping a second referer. A blank referer in any casing is an
explicit opt-out and drops all three.

No environment variables: every other per-Provider knob lives on the Provider
row, and the existing Headers field already gives per-Workspace override and
opt-out, which env vars would only duplicate at coarser granularity.

Documents the previously undocumented Provider Headers field along the way.

Closes #426

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@willdady
willdady merged commit ba09d0f into main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenRouter app attribution headers

1 participant