Authenticating an agent with Yappy

This document is written for an autonomous agent that has landed on yappy.biz and needs to know whether it must authenticate, and if so, how.

Read this first: you almost certainly do not need a credential. Every read endpoint under https://yappy.biz/api/v1 and both MCP servers are public and unauthenticated. Try the call before you try to authenticate.

Credentials exist for exactly two reasons: a higher rate ceiling, and the export:docs scope. Nothing behind them is secret.

Discover

Start from one request. Probing a protected entry point returns a 401 whose header names the metadata document:

GET /agent/auth HTTP/1.1
Host: yappy.biz
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="yappy", resource_metadata="https://yappy.biz/.well-known/oauth-protected-resource", scope="read:releases read:docs"

Follow resource_metadata:

DocumentURLSpec
Protected resource metadatahttps://yappy.biz/.well-known/oauth-protected-resourceRFC 9728
Authorization server metadatahttps://yappy.biz/.well-known/oauth-authorization-serverRFC 8414

The protected-resource document lists authorization_servers, scopes_supported, and bearer_methods_supported. The authorization-server document carries the endpoints, code_challenge_methods_supported, and an agent_auth block whose skill field points back at this file.

Pick a method

The agent_auth block in the authorization-server metadata advertises which identity types this deployment accepts:

{
  "agent_auth": {
    "skill": "https://yappy.biz/auth.md",
    "register_uri": "https://yappy.biz/oauth/register",
    "claim_uri": "https://yappy.biz/oauth/token",
    "revocation_uri": "https://yappy.biz/oauth/revoke",
    "identity_types_supported": ["anonymous", "identity_assertion"],
    "anonymous": {
      "credential_types_supported": ["oauth_client_credentials"]
    },
    "identity_assertion": {
      "assertion_types_supported": [
        "urn:ietf:params:oauth:token-type:id-jag",
        "urn:ietf:params:oauth:token-type:jwt"
      ],
      "credential_types_supported": ["oauth_client_credentials"]
    }
  }
}

**Choose anonymous** unless the credential must be attributable to a specific human. Yappy's API serves public product data; there is no per-user state to protect, so an anonymous client credential is the correct, least-privilege choice and it needs no browser and no human.

Choose identity_assertion — presenting an id-jag or a JWT from an identity provider you already hold — when your platform requires every outbound call to be attributable.

Register

RFC 7591 dynamic client registration. No human approval, no waiting.

POST /oauth/register HTTP/1.1
Host: yappy.biz
Content-Type: application/json

{
  "client_name": "example-agent",
  "grant_types": ["client_credentials"],
  "token_endpoint_auth_method": "client_secret_basic",
  "scope": "read:releases read:docs read:pricing",
  "software_id": "example-agent",
  "contacts": ["[email protected]"]
}
{
  "client_id": "yc_…",
  "client_secret": "ys_…",
  "client_id_issued_at": 1787000000,
  "client_secret_expires_at": 0,
  "grant_types": ["client_credentials"],
  "scope": "read:releases read:docs read:pricing",
  "registration_client_uri": "https://yappy.biz/oauth/register"
}

Store client_secret. It is not retrievable later; losing it means registering again, which is free.

Claim

Exchange the registration for an access token at the claim_uri:

POST /oauth/token HTTP/1.1
Host: yappy.biz
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=yc_…&client_secret=ys_…&scope=read:releases
{
  "access_token": "yat_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:releases"
}

If the credential must be tied to a person, use the authorization-code grant instead. **PKCE with S256 is required**; plain is refused.

GET /oauth/authorize
  ?response_type=code
  &client_id=yc_…
  &redirect_uri=https://example.com/callback
  &scope=read:docs
  &state=<opaque>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

Exchange the returned code at /oauth/token with grant_type=authorization_code and the original code_verifier.

Use the credential

Bearer token in the Authorization header. That is the only accepted presentation — no query parameter, no cookie.

GET /api/v1/releases HTTP/1.1
Host: yappy.biz
Authorization: Bearer yat_…

Scopes, all of them read-shaped:

ScopeGrants
read:releasesRelease history and the current version
read:docsDocumentation pages and search
read:pricingPricing and plan data
read:faqFrequently asked questions
export:docsStarting and polling export jobs

Request the narrowest set that does the job. A token raises your ceiling from 120 to 600 requests per minute.

Errors

Every failure is a JSON document, never an HTML page.

StatusHeadercodeDo this
401WWW-Authenticate: Bearer error="invalid_token"invalid_tokenGet a new token. Do not retry with the old one.
401WWW-Authenticate: Bearer resource_metadata="…"unauthorizedYou sent no token. Read the metadata and register.
403WWW-Authenticate: Bearer error="insufficient_scope", scope="export:docs"insufficient_scopeRe-issue a token with the scope named in the header.
400invalid_requestRead errors[]; a parameter is malformed.
429Retry-After: <seconds>rate_limitedWait, then retry. Batch your reads.

Full list: <https://yappy.biz/developers/errors>.

Revocation

RFC 7009, at the revocation_uri:

POST /oauth/revoke HTTP/1.1
Host: yappy.biz
Content-Type: application/x-www-form-urlencoded

token=yat_…&token_type_hint=access_token&client_id=yc_…&client_secret=ys_…

Returns 200 whether or not the token existed, as the RFC requires — a caller cannot use this endpoint to probe which tokens are real.

Revoke when: the agent's task is finished and the credential is no longer needed, the secret may have been exposed, or your own client is being retired. Revoking a client's secret invalidates every token issued under it.

There is nothing to clean up on our side beyond that. We store no user data against a client, because the API exposes none.

Reading this as an agent? The same page in markdown: /auth.md · machine index: /llms.txt · API: /openapi.json · MCP: https://yappy.biz/mcp