# Yappy API authentication

**Most of this API needs no credentials.** Every read endpoint under
`/api/v1`, and both MCP servers, are open. Start there and only come back to this
page if you hit a rate ceiling.

## Discovery first

An agent should not guess. The two metadata documents describe everything:

| Document | URL |
|---|---|
| Protected resource metadata (RFC 9728) | [`/.well-known/oauth-protected-resource`](https://yappy.biz/.well-known/oauth-protected-resource) |
| Authorization server metadata (RFC 8414) | [`/.well-known/oauth-authorization-server`](https://yappy.biz/.well-known/oauth-authorization-server) |
| Prose walkthrough for agents | [`/auth.md`](https://yappy.biz/auth.md) |

Probing `https://yappy.biz/agent/auth` returns a `401` whose
`WWW-Authenticate: Bearer resource_metadata="…"` header points at the first of
those, so one request is enough to learn the rules.

## Register a client

RFC 7591 dynamic client registration, no human in the loop:

```bash
curl -sX POST https://yappy.biz/oauth/register \
  -H 'content-type: application/json' \
  -d '{
    "client_name": "my-agent",
    "grant_types": ["client_credentials", "authorization_code"],
    "response_types": ["code"],
    "redirect_uris": ["https://example.com/callback"],
    "token_endpoint_auth_method": "client_secret_basic",
    "scope": "read:releases read:docs read:pricing"
  }'
```

The response carries `client_id`, `client_secret`, and the issued scopes. Store
the secret; it is not retrievable afterwards.

## Get a token

Machine-to-machine, which is what an agent wants:

```bash
curl -sX POST https://yappy.biz/oauth/token \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials&client_id=…&client_secret=…&scope=read:releases'
```

Authorization code with **PKCE S256** is also supported, for the case where a
token should be tied to a person rather than a program:

```
GET /oauth/authorize
  ?response_type=code
  &client_id=…
  &redirect_uri=https://example.com/callback
  &scope=read:docs
  &state=…
  &code_challenge=…
  &code_challenge_method=S256
```

`S256` is the only challenge method accepted. Plain is refused.

## Scopes

Least privilege is machine-readable — the same list appears in
`scopes_supported` in the protected-resource metadata and in the OpenAPI security
schemes.

| Scope | Grants |
|---|---|
| `read:releases` | Release history and the current version |
| `read:docs` | Documentation pages and search |
| `read:pricing` | Pricing and plan data |
| `read:faq` | Frequently asked questions |
| `export:docs` | Starting and polling export jobs |

Ask for the narrowest set that does the job. A token with no scope still reads
everything that is public — the scopes exist to raise limits and to gate exports,
not to unlock secrets.

## Use the credential

```bash
curl -s https://yappy.biz/api/v1/releases \
  -H 'Authorization: Bearer <access_token>'
```

Bearer in the `Authorization` header is the only accepted method — no query
parameter, no cookie. Tokens are opaque, expire in one hour, and carry the scopes
they were issued with.

## Errors

`401` with `WWW-Authenticate: Bearer error="invalid_token"` means expired or
malformed — get a new one. `403` with `error="insufficient_scope"` names the
scope you needed in the header. Both bodies are RFC 9457 problem documents; see
[errors](https://yappy.biz/developers/errors).

## Revocation

RFC 7009:

```bash
curl -sX POST https://yappy.biz/oauth/revoke \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'token=<access_token>&client_id=…&client_secret=…'
```

Returns `200` whether or not the token existed, as the RFC requires. Revoking a
client's secret revokes every token issued under it.

---

Canonical: https://yappy.biz/developers/authentication/ · Last updated: 2026-08-25
