# NegativeEV auth.md

NegativeEV (https://negativeev.com) is a bet checker. Anonymous visitors get 10
free checks a day; signing in makes checks unlimited (paced by a per-hour cap).
There is no separate agent API — agents use the same HTTP endpoints as the web
app, under the same quotas and rate limits. The MCP server is the one exception:
it is metered on its own counter, more generously (see below).

## How auth works

- The authorization server is Supabase, not this origin. Its OIDC discovery
  metadata lives at
  https://bnmlyacvflqcwslhcxua.supabase.co/auth/v1/.well-known/openid-configuration
  and is mirrored at this origin for discovery:
  https://negativeev.com/.well-known/openid-configuration and
  https://negativeev.com/.well-known/oauth-authorization-server
- negativeev.com is the protected resource: it verifies Supabase-issued JWTs
  sent as `Authorization: Bearer <access token>`. Machine-readable resource
  metadata (RFC 9728): https://negativeev.com/.well-known/oauth-protected-resource
- Humans sign in with an email one-time code at https://negativeev.com/

## What the authorization server supports

Read from Supabase's own discovery document, which is the authority; this list
is a convenience, not a second source of truth.

- Grants: `authorization_code` and `refresh_token`. There is no
  `client_credentials` grant — see the next section for what that means.
- PKCE is supported with `S256`. Use it: an agent driving a browser has no
  place to keep a client secret.
- Scopes: `openid`, `profile`, `email`, `phone`, `offline_access`. This origin
  needs only `openid profile email` to identify the caller behind a check;
  ask for `offline_access` only if you genuinely need to refresh unattended.
- Tokens are RS256/HS256/ES256 JWTs, sent to this origin as
  `Authorization: Bearer`. A protected endpoint refuses an invalid or expired
  one with 401 and a `WWW-Authenticate: Bearer` header naming the
  resource metadata above — re-authenticate rather than retrying anonymously.

## What this means for agents

- An agent acts ON BEHALF OF a signed-in user, always. There is no
  `client_credentials` grant: nothing here issues a token to a program acting
  as itself. The one credential a program can hold is an agent key a member
  minted for it (next section), and that key carries the member's own
  entitlements, not a separate identity.
- Dynamic client registration (RFC 7591) is supported, so an MCP host can
  register itself without anyone creating a client by hand. Registration alone
  grants nothing — every authorization is decided by the signed-in user on
  https://negativeev.com/oauth/consent, which names the client and the scopes
  it asked for. The `registration_endpoint` is in the authorization server
  metadata linked above.
- The simpler path still works and needs no registration: send a signed-in
  user's Supabase access token as `Authorization: Bearer` on requests
  (including MCP tool calls), or drive the site in the user's browser session.
- Anonymous access also works; the free daily quota is metered per device.

## Agent keys (optional, members only)

A signed-in member can mint a key that carries the same entitlements as their
web session — the same hourly allowance, the same sims-explorer access — so an
agent, a script or an MCP client can act as them without holding a Supabase
session. Anonymous access stays keyless; a key is never required.

- Mint: `POST https://negativeev.com/keys` with a Supabase access token as
  `Authorization: Bearer` and the body `{"name": "<a label>"}`. Answers
  `{"id", "name", "prefix", "key", "created_at"}`. The `key` (`nev_` followed
  by 43 characters) is shown exactly once; only its SHA-256 is stored.
- Use: send it as `Authorization: Bearer nev_…` on any request this document
  covers, MCP tool calls included. Once revoked it authenticates nothing: the
  metered routes treat the call as anonymous, the MCP endpoint refuses it with
  401, and the member-only routes answer 401.
- List: `GET https://negativeev.com/keys` — ids, names, prefixes and dates,
  never a plaintext or a hash.
- Revoke: `POST https://negativeev.com/keys/revoke` with `{"id": "<key id>"}`.
- A key cannot mint, list or revoke keys: those three routes take a Supabase
  access token only, and answer 403 to a key.

## MCP server

A stateless MCP server (Streamable HTTP transport) is available at
`POST https://negativeev.com/mcp` — server card at
https://negativeev.com/.well-known/mcp/server-card.json

Tools: `check_bet` (grade a bet from text — spends one of the caller's checks
when it fully grades), `get_slate` (is there a checkable slate right now —
free), `get_usage` (checks remaining — free). Anonymous tool calls get their own
per-device allowance of 100 checks a day — a separate counter from the web app's
10, so agent traffic never eats a browser's free checks. Send a Supabase access
token as `Authorization: Bearer` on the POST for unlimited checks (paced at 1000
an hour).

### Example prompts

Prompts a user can give any MCP-connected assistant to exercise the tools:

- "Is Shohei Ohtani over 1.5 total bases at -120 a good bet tonight?" —
  `check_bet` grades it against thousands of simulated games and returns the
  true probability, the implied probability from the price, and the edge.
- "Is there a checkable MLB slate tonight?" — `get_slate` answers per live
  sport, free, before any check is spent.
- "How many bet checks do I have left today?" — `get_usage` reports the
  remaining quota and when the next check frees up.
- "Here's my parlay: Ohtani over 0.5 home runs +280 and Dodgers moneyline
  -150. Worth it?" — `check_bet` prices each leg and the combined parlay.

## Endpoints agents may call

Same-origin JSON, quota- and rate-limited, not yet documented as a stable
public API (shapes can change without notice):

- `GET /slate` — is there a checkable slate right now?
- `POST /extract` — parse a bet from text: `{"text": "<bet text>"}`
- `POST /check` — grade the parsed legs (send the fields `/extract` returned)
- `GET /usage` — checks remaining for the caller

A refused request (quota, rate limit, started game) returns an explanatory
JSON error. Discovery: /.well-known/api-catalog, /sitemap.xml, and the
agent-skills index at /.well-known/agent-skills/index.json
