# AnimaGuru auth.md

How an agent gets credentials for AnimaGuru (https://animaguru.com). There are two protected surfaces, both on `animaguru.com`, and the same host is the authorization server:

- The **MCP server** at `https://animaguru.com/mcp` uses OAuth 2.1: the authorization code grant with PKCE and dynamic client registration.
- The **REST API** under `https://animaguru.com/api/v1` uses bearer tokens from the OAuth 2.0 device authorization grant (RFC 8628). The `animaguru` CLI runs this grant for you.

Every token acts for a person who signs in to AnimaGuru and approves the agent, with that person's projects and credits. AnimaGuru does not issue credentials to an agent on its own.

## Discover

An MCP request without a valid token returns 401 with a challenge that points at the protected resource metadata:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://animaguru.com/.well-known/oauth-protected-resource/mcp", scope="projects.read projects.write"
```

Protected resource metadata (RFC 9728), also at `/.well-known/oauth-protected-resource`:

```json
{
  "resource": "https://animaguru.com/mcp",
  "resource_name": "AnimaGuru MCP server",
  "authorization_servers": ["https://animaguru.com"],
  "scopes_supported": ["projects.read", "projects.write"],
  "bearer_methods_supported": ["header"]
}
```

Authorization server metadata (RFC 8414) at `https://animaguru.com/.well-known/oauth-authorization-server`:

```json
{
  "issuer": "https://animaguru.com",
  "authorization_endpoint": "https://animaguru.com/api/oauth/authorize",
  "token_endpoint": "https://animaguru.com/api/oauth/token",
  "registration_endpoint": "https://animaguru.com/api/oauth/register",
  "revocation_endpoint": "https://animaguru.com/api/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["none"],
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["projects.read", "projects.write"]
}
```

The REST API answers 401 with `WWW-Authenticate: Bearer realm="AnimaGuru API"` and `Link: </auth.md>; rel="help"`. Its operations are described in [openapi.json](https://animaguru.com/openapi.json).

Scopes: `projects.read` lists and inspects workspaces, projects, assets and jobs, and estimates costs for free. `projects.write` creates projects and queues generation jobs, which spend credits.

## Pick a method

| Your agent | Method |
| --- | --- |
| An MCP client that can open a browser for the person | OAuth 2.1 authorization code with PKCE, below |
| A CLI, script or headless agent calling the REST API | Device authorization grant, below, or `animaguru auth login` |

Not supported: the `agent_auth` extension. There is no `identity_endpoint`, no `identity_assertion` exchange (including the ID-JAG token type `urn:ietf:params:oauth:token-type:id-jag`), no `service_auth` and no anonymous registration. AnimaGuru also has no API keys or client-credentials grant. A person always approves access in the browser.

## Register

MCP clients register themselves (RFC 7591). Redirect URIs must use HTTPS, or HTTP on `localhost` or `127.0.0.1`, with no fragment:

```http
POST /api/oauth/register
Content-Type: application/json

{"client_name": "My agent", "redirect_uris": ["https://agent.example/callback"]}
```

```json
{"client_id": "…", "client_name": "My agent", "redirect_uris": ["https://agent.example/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"]}
```

Clients are public: there is no client secret, so PKCE protects the exchange. The device authorization grant needs no registration; use the public client ID `animaguru-cli`.

## Claim

There is no separate claim step: the person approves the agent in their browser.

For OAuth, send the person to the authorization endpoint. They sign in if needed and see a consent page with your client name and the scopes:

```http
GET /api/oauth/authorize?response_type=code&client_id=…&redirect_uri=https%3A%2F%2Fagent.example%2Fcallback&code_challenge=…&code_challenge_method=S256&scope=projects.read%20projects.write&resource=https%3A%2F%2Fanimaguru.com%2Fmcp&state=…
```

`scope` and `resource` are optional and default to both scopes and the MCP server. After approval the browser returns to `redirect_uri` with `code` and `state`.

For the device grant, request a code and show the person `verification_uri_complete` (where they sign in and approve) and the `user_code`:

```http
POST /api/auth/device/code
Content-Type: application/json

{"client_id": "animaguru-cli"}
```

```json
{"device_code": "…", "user_code": "…", "verification_uri": "https://animaguru.com/cli/authorize", "verification_uri_complete": "https://animaguru.com/cli/authorize?user_code=…", "expires_in": 600, "interval": 5}
```

## Exchange

OAuth: exchange the code with the PKCE verifier. Access tokens last one hour; refresh tokens last 30 days and rotate on use.

```http
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=…&redirect_uri=https%3A%2F%2Fagent.example%2Fcallback&client_id=…&code_verifier=…
```

```http
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=…&client_id=…
```

Device grant: poll every `interval` seconds until the person approves. The access token is a seven-day session.

```http
POST /api/auth/device/token
Content-Type: application/json

{"grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "…", "client_id": "animaguru-cli"}
```

## Use the access_token

Send it in the `Authorization` header. Tokens in query strings or bodies are not accepted.

```http
POST /mcp
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream
```

```http
GET /api/v1/credits
Authorization: Bearer <access_token>
```

Generation spends the workspace's credits. Get a free estimate first (the `estimate_generation` tool or an `/estimate` endpoint) and confirm with the person before queueing paid work.

## Errors

| Error | Endpoint | What to do |
| --- | --- | --- |
| 401 `invalid_token` | `/mcp` | The token expired or was revoked. Refresh it, or start authorization again. |
| 401 | `/api/v1/*` | Missing or expired token. Run the device grant again. |
| `invalid_redirect_uri`, `invalid_client_metadata` | `/api/oauth/register` | Send one to ten HTTPS redirect URIs (or HTTP on localhost), `token_endpoint_auth_method` `none`, and only the supported grant types. |
| `invalid_request`, `access_denied` | `/api/oauth/authorize` | Check the parameters, or the person declined. |
| `invalid_grant` | `/api/oauth/token` | The code or refresh token is used, expired or does not match the verifier, redirect URI or client. Start authorization again. |
| `unsupported_grant_type` | `/api/oauth/token` | Use `authorization_code` or `refresh_token`. |
| `authorization_pending`, `slow_down` | `/api/auth/device/token` | Keep polling; after `slow_down`, wait longer between polls. |
| `expired_token`, `access_denied` | `/api/auth/device/token` | Request a new device code. |
| 429 | `/api/oauth/*`, `/api/auth/*` | Too many attempts. Wait a minute. |

## Revocation

OAuth tokens (RFC 7009):

```http
POST /api/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=…&client_id=…
```

CLI and REST API sessions end with `animaguru auth logout`, or `POST /api/auth/sign-out` with the bearer token.

## More

- [Developer docs](https://animaguru.com/docs.md)
- [Terms of service](https://animaguru.com/terms-of-service.md) and [privacy policy](https://animaguru.com/privacy-policy.md)
- Support: hello@animaguru.com. Never send passwords or access tokens.
