# auth.md — OpenBoss API

> **This API requires no authentication.** There is no API key, no OAuth, no
> client registration, no account and no sign-up. Every read endpoint is open to
> anonymous requests, and the free half of the game is playable without any
> credential at all. Nothing below asks you to obtain one, because there is
> nothing to obtain.

Operated by Flisko — Informacijske storitve, Žiga Flis s.p. — human contact: ziga@flisko.si.

## Summary

| Question | Answer |
| --- | --- |
| Authentication scheme | None. Requests are anonymous. |
| API key | Does not exist. There is no key programme and no way to request one. |
| OAuth / OIDC | Not implemented anywhere in this service. |
| Account or sign-up | Does not exist. There are no user accounts. |
| Required headers | None. `Content-Type: application/json` on POST bodies. |
| Cost of reading | Free. |
| Sandbox / test environment | None is published. See "What does not exist" below. |
| Machine purchasing | Not available. Payment requires a person at Stripe Checkout. |

## Base URL

```
https://api.openboss.lol
```

All endpoints below are relative to that origin.

## Authentication methods

### 1. Anonymous — every public read endpoint

Send the request. Add nothing.

```
curl https://api.openboss.lol/state
```

No `Authorization` header is read on any of these routes, and sending one
changes nothing about the response.

### 2. Anonymous — free play

Landing a free smack needs no credential either. `POST /smash` accepts a
`visitorId`: an opaque string you generate yourself (8–128 characters, a UUID
is ideal) and reuse so the free allowance can be counted. It is **not** a
credential — nobody issues it, nothing validates it against a store of known
ids, and inventing a new one is not an authentication bypass. It is a counter
key, and it is the only reason a free allowance can exist without accounts.

Each `visitorId` gets **3 lifetime free smacks** (tracked for 30 days), plus
**one extra free swing per UTC day** once those are gone. Exhausting today's
returns `FREE_EXHAUSTED`, which is the allowance working rather than a fault;
the daily one comes back at midnight UTC.
A free smack can never land the killing blow, and free damage is capped per
boss across everyone — both rules are enforced inside the database, not in the
HTTP layer.

### 3. Wallet token — spending smacks somebody paid for

A purchase produces a **wallet token**: `<id>.<HMAC-SHA256 signature>`, issued
by `GET /purchase/:sessionId` after Stripe reports the payment settled. It
authorises exactly one thing — spending the smacks already bought under that id.

Two things about it that matter, because it is the closest thing here to a
credential and it is easy to describe wrongly:

- **It travels in the JSON body as `token`, not in an `Authorization`
  header.** `POST /smash` reads `body.token`. A bearer header is ignored.
- **It is issued by a completed payment, not by an authorization server.** There
  is no endpoint that mints one, no refresh, no expiry flow, no scopes and no
  way to obtain one without buying something. Whoever holds it holds the
  smacks; there is no account behind it to recover it from.

A `token` field that is present but unusable returns `401 BAD_TOKEN`. It is
never silently downgraded to a free hit.

### 4. Owner token — recolouring The Pixel

`POST /pixel/color` takes a similar signed handle, issued to the winning buyer
by `GET /pixel/purchase/:sessionId`, also in the JSON body as `token`. It
names a buyer; it does not assert ownership on its own. The current owner is
compared inside the database at the moment of the write, so a past owner's token
stays genuine, stays permanently valid, and is worth nothing —
`403 NOT_THE_OWNER`. The Pixel is documented at https://thepixel.lol.

## Endpoints

Read endpoints. No credential, no header, no cost.

| Method | Path | Auth | Notes |
| --- | --- | --- | --- |
| GET | `/state` | None | The whole arena in one call: boss, leaderboard, hit feed, pack prices, live momentum, crit odds. `Cache-Control: no-store`. |
| GET | `/graveyard?limit=24&offset=0` | None | Dead bosses, newest first, with the killer's display name. Paginated; `total` is in the response. |
| GET | `/pixel` | None | State of the sibling product: owner, price, colour, sale history. |
| GET | `/events` | None | Server-Sent Events stream. Use this instead of polling `/state`. |
| GET | `/api/health` | None | Readiness probe. |
| POST | `/mcp` | None | Model Context Protocol server, JSON-RPC 2.0 over Streamable HTTP. See below. |

Write endpoints. Still no API key — but read what each one does first.

| Method | Path | Auth | Notes |
| --- | --- | --- | --- |
| POST | `/smash` | `visitorId` (free) or `token` (paid), in the body | One swing per call. |
| POST | `/checkout` | None | Creates a Stripe Checkout Session against a **real card**. |
| GET | `/purchase/:sessionId` | None | Turns a settled session into a wallet token. |
| POST | `/pixel/checkout` | None | As above, for The Pixel. |
| GET | `/pixel/purchase/:sessionId` | None | As above, for The Pixel. |
| POST | `/pixel/color` | Owner `token`, in the body | Sets the pixel's colour. |

### The MCP endpoint

`POST https://api.openboss.lol/mcp` is a real Model Context Protocol server and it is
**unauthenticated**. It is stateless: no `Mcp-Session-Id`, no session to
establish, no server-initiated stream. `GET /mcp` answers `405` with a
pointer, deliberately, rather than `404`.

Because it does no authentication, it publishes no OAuth protected-resource
metadata and returns no `WWW-Authenticate` challenge. There is nothing to
challenge for. Its card is at
[`https://openboss.lol/.well-known/mcp/server-card.json`](https://openboss.lol/.well-known/mcp/server-card.json),
which carries no `authentication` block for the same reason.

## Rate limits

There are no per-key quotas, because there are no keys. Limits are per IP
address (or, on `/smash`, per wallet), applied per endpoint, and they are the
only budget that exists.

| Bucket | Limit | Applies to | Keyed on |
| --- | --- | --- | --- |
| `state` | 240 / minute | `GET /state`, `GET /pixel` | IP |
| `events` | 90 / 5 minutes | `GET /events` | IP |
| `mcp` | 180 / minute | `POST /mcp` | IP |
| `smash` | 120 / minute | `POST /smash` | Wallet id, else IP + `visitorId` |
| `smash-free` | 10 / hour | `POST /smash`, free hits only | IP |
| `checkout` | 20 / minute | `POST /checkout`, `POST /pixel/checkout` | IP |
| `pixel-color` | 20 / minute | `POST /pixel/color` | IP |
| `default` | 120 / minute | `GET /purchase/:id`, `GET /pixel/purchase/:id` | IP |

`GET /graveyard` and `GET /api/health` have no per-endpoint limit configured
at present. Do not read that as a guarantee — it is the current state, not a
promise, and either may gain one. Poll them politely.

A request through `POST /mcp` that lands a free smack still spends from
`smash-free`: the MCP tools call the same code the website does, so the
transport limit above does not replace the limits underneath it.

Successful responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and
`X-RateLimit-Reset`. A `429` carries `Retry-After` in seconds — read it
rather than guessing a backoff.

## Error responses

Every error is JSON with an `error` string and a stable `code`.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `BAD_REQUEST`, `BAD_JSON`, `BAD_VISITOR_ID`, `BAD_PACK` | Malformed request. Fix the body. |
| 401 | `BAD_TOKEN` | A wallet or owner token was supplied and is not valid. **Not** a prompt to authenticate — an anonymous request to the same endpoint is fine. |
| 403 | `NOT_THE_OWNER` | A genuine pixel owner token whose holder has since been outbid. |
| 404 | `NOT_FOUND` | No such route, or no such purchase. |
| 429 | `RATE_LIMITED` | Over a bucket above. Honour `Retry-After`. |
| 501 | `FEATURE_DISABLED` | That half of the product is switched off in this deployment. |
| 503 | `STATE_UNAVAILABLE`, `GRAVEYARD_UNAVAILABLE` | A dependency is down. Retry. |

**No endpoint on this API ever returns `WWW-Authenticate`.** A `401` here
always means "the token you sent is broken", never "authenticate and retry" —
there is no scheme to retry with. If you are writing a client that reacts to
`401` by starting an auth flow, that branch is unreachable against this API.

## Paid actions

Buying is not available as a machine API, and this is a design constraint rather
than a missing feature.

`POST /checkout` returns a Stripe Checkout Session **URL**. Completing it
requires card data, which this service never sees, holds or forwards — so there
is no version of that call that also pays. Hand the URL to the person. They open
it, they read the disclosures on that page (all sales final, no prizes, no
payouts of any kind), and they enter their own card. Then poll
`GET /purchase/:sessionId` until it reports paid, and you receive a wallet
token to spend.

Spending an existing wallet is different from filling one, and `POST /smash`
will spend freely: the money is already gone and using the smacks is the product
working.

**Buying The Pixel is deliberately not offered as a tool at all.** A bid that
loses a simultaneous takeover is charged in full and refunds nothing — a rule a
person has to meet before they pay, not one an agent should be able to walk them
into. Reading the pixel is an endpoint; buying it is a link to https://thepixel.lol.

## Calling this from a browser

Server-side requests are accepted from anywhere: curl, a scheduled job, your own
backend. **Browser requests from another origin are not.** The API matches the
`Origin` header against an exact allow-list, so a page on an origin that is not
on it cannot read these endpoints from script. A request with no `Origin`
header at all — which is what a server-side fetch sends — is what goes through.

This is CORS, not authentication. It is not a credential check and there is no
header you can add to satisfy it. Fetch this from your own backend.

## What does not exist

Listed explicitly, because "absent" and "undocumented" look identical from
outside and only one of them is safe to plan around.

- **No OAuth 2.0, anywhere in this service.** No authorization server, no token
  endpoint, no client registration, no scopes, no grants, no refresh.
- **No `/.well-known/oauth-protected-resource`** (RFC 9728) and no
  `/.well-known/oauth-authorization-server` (RFC 8414). Both would name an
  authorization server. There is none to name, and a client that followed such a
  document would dead-end. Their absence is deliberate.
- **No API keys.** No key programme, no application form, no partner tier, no
  rate-limit upgrade to buy. Requests are anonymous and everyone gets the
  buckets above.
- **No sandbox and no test environment.** https://api.openboss.lol is the only deployment there
  is. `POST /checkout` creates a session against real Stripe infrastructure and
  `POST /smash` spends smacks somebody paid for, so do not call either to
  explore the API. Read endpoints and `POST /mcp`'s read tools are free and
  safe to try; use those.
- **No user accounts, no login, no session cookies for API callers.** A wallet
  lives in one browser or in whoever holds the token.
- **No webhooks you can subscribe to.** `POST /webhooks/stripe` exists and is
  Stripe's, verified against a signing secret; it is not a subscription service
  and there is no way to register a URL with us. Use `GET /events` for live
  updates.
- **No OpenAPI document.** The API is described in prose here and in
  [`/llms.txt`](https://openboss.lol/llms.txt), and structurally in
  [`/.well-known/api-catalog`](https://openboss.lol/.well-known/api-catalog). None of those
  is a schema a client generator can consume, and none of them is advertised as
  one.

## Credentials that exist, and are not for you

For completeness, so that finding one of these in a response or a header is not
mistaken for an integration path:

- **`/admin/*` requires `Authorization: Bearer <ADMIN_API_TOKEN>`** and
  returns `401` without it. That token is a single operator secret held in an
  environment variable. There is no issuer, no grant, no registration and no
  process by which a third party could obtain one — so no resource metadata is
  published for it and no `WWW-Authenticate` challenge is sent. It is not an
  API tier and it is not something to request.
- **An internal header exempts our own server-side rendering from rate
  limiting.** Its value is deliberately never exposed to any browser bundle. It
  is infrastructure, not an integration.

Neither is documented further, neither is obtainable, and neither appears in any
discovery document this site publishes.

## Contact

One address, read by a person: ziga@flisko.si. Also at
[https://openboss.lol/contact](https://openboss.lol/contact).

If you are integrating and something above is wrong, out of date, or does not
match what the API actually did, that is worth an email — a false sentence in
this file is a bug of the same severity as a broken endpoint.
