# auth.md

How agents authenticate with Bison Labs.

## TL;DR: you probably don't need to authenticate

**Booking a call requires no account, no login, and no token.** A human books through the website
form with just a name and an email. Agents get exactly the same deal:

- `GET  https://bisonlabs.dev/api/cal/slots?date=YYYY-MM-DD`: free slots
- `POST https://bisonlabs.dev/api/cal/book`: `{ start, name, email }`

Both are open. See [the OpenAPI description](https://bisonlabs.dev/openapi.json) and
[the docs](https://bisonlabs.dev/docs/api).

Authentication exists only for the **MCP endpoint**, because the Model Context Protocol expects
it, not because the capability behind it is privileged. It offers the same three actions.

## Agent registration

The identity we issue is the **agent's**, not a person's. There is no user account, we store no
profile, and no human is asked to approve anything.

Register with OAuth 2.0 Dynamic Client Registration ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)):

```
POST https://bisonlabs.dev/register
Content-Type: application/json

{
  "client_name": "your-agent",
  "redirect_uris": ["https://your-agent.example/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "client_secret_post"
}
```

Then run the standard OAuth 2.1 authorization-code flow with PKCE (S256 required):

| Endpoint | URL |
| --- | --- |
| Authorization server metadata | `https://bisonlabs.dev/.well-known/oauth-authorization-server` |
| Protected resource metadata | `https://bisonlabs.dev/.well-known/oauth-protected-resource/mcp` |
| Register | `https://bisonlabs.dev/register` |
| Authorize | `https://bisonlabs.dev/authorize` |
| Token | `https://bisonlabs.dev/token` |
| MCP (Streamable HTTP) | `https://bisonlabs.dev/mcp` |

`/authorize` **auto-approves**. There is no consent screen and no login prompt. Asking a human to
approve something no human initiated would be theatre, and it would hold agents to a higher bar
than the people using the website.

Include the [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) `resource` parameter
(`https://bisonlabs.dev/mcp`) in both the authorization and token requests; tokens are
audience-bound and will be rejected without it.

## Supported methods

| | |
| --- | --- |
| Identity types | `anonymous` (the client, not a user) |
| Credential types | `client_secret_post`, `client_secret_basic`, `none` (public clients) |
| Grant types | `authorization_code`, `refresh_token` |
| PKCE | Required, `S256` only |
| Scopes | `services:read`, `availability:read`, `booking:write` |
| Access token lifetime | 1 hour |

`client_credentials` is **not** supported. A headless agent with no browser should use the open
REST API above, which needs no token at all.

## What the token is, and what it isn't

Be clear about this, because the word "OAuth" implies more than is true here:

**The access token is not a security boundary.** Registration is open and authorization is
automatic, so anyone can obtain a token. It grants nothing a stranger could not already do through
the public website form.

What it *is* good for:

- **A stable per-agent identifier** for rate limiting, materially better than an IP address,
  since agents share cloud egress IPs and would otherwise throttle each other.
- **Revocation**: a misbehaving `client_id` can be blocked.

Abuse is prevented by rate limiting (30/min availability, 3/min booking), not by this flow.

## Rate limits

| Endpoint | Limit |
| --- | --- |
| `GET /api/cal/slots`, MCP `check_availability` | 30 / minute |
| `POST /api/cal/book`, MCP `book_call` | 3 / minute |

Exceeding a limit returns `429` with a `Retry-After` header. REST callers are keyed by IP; MCP
callers by `client_id`.

## Contact

A human reads [hello@bisonlabs.dev](mailto:hello@bisonlabs.dev).
