# ViralNote agent authentication

This document follows the WorkOS auth.md convention (https://github.com/workos/auth.md). It tells an agent how to get a credential for the ViralNote API and MCP server, what it can do with it, and how it fails.

## Discover

- Protected resource metadata (RFC 9728): `https://dashboard.viralnote.app/.well-known/oauth-protected-resource` (MCP) and `https://dashboard.viralnote.app/.well-known/oauth-protected-resource/api/v1` (REST). A mirror for the marketing origin lives at `https://www.viralnote.app/.well-known/oauth-protected-resource`.
- Authorization server metadata (RFC 8414): `https://dashboard.viralnote.app/.well-known/oauth-authorization-server`. Its `agent_auth` block points `skill` at this file.
- Unauthenticated calls to `https://dashboard.viralnote.app/api/v1` or `https://dashboard.viralnote.app/api/mcp/mcp` return `401` with `WWW-Authenticate: Bearer resource_metadata="<url>"` so you can find this metadata from a single request.
- Scopes (`scopes_supported`): `posts:read`, `posts:write`, `posts:publish`, `webhooks:manage`, `credits:read`, `credits:write`.

## Pick a method

| Method | When to use | Human in the loop |
|--------|-------------|-------------------|
| **OAuth 2.0 authorization code + PKCE** | MCP clients and hosted agents that can open a browser (Claude, ChatGPT, Cursor, Smithery). Dynamic client registration is open. | Once, at connect time |
| **API key (`vnd_...`)** | Scripts, cron jobs, self-hosted agents, stdio MCP. | Once, when the user creates the key |
| **identity_assertion (ID-JAG)** | Not supported yet. `identity_types_supported` in the AS metadata lists only what the server accepts today; check it before minting an assertion. | — |
| **service_auth / anonymous** | Not supported yet. | — |

Both supported methods yield the same principal and scopes; choose by whether a browser is available.

## Register

**OAuth:** `POST https://dashboard.viralnote.app/api/mcp/oauth/register` (RFC 7591) with `{ "client_name": "...", "redirect_uris": ["..."], "grant_types": ["authorization_code","refresh_token"], "token_endpoint_auth_method": "none" }`. The response contains `client_id`. No secret is issued; PKCE is required.

**API key:** the user signs in at https://dashboard.viralnote.app (7-day Publish trial; card required to unlock publishing; permanent email required) and creates a key at https://dashboard.viralnote.app/developers/auth, choosing scopes and an optional per-minute rate limit. Keys are shown once and stored hashed. Agents cannot create keys without a signed-in user; there is no `identity_endpoint` for unattended registration today.

## Claim

**OAuth:** send the user to `https://dashboard.viralnote.app/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&scope=posts:read%20posts:write&code_challenge=...&code_challenge_method=S256&state=...`. The user signs in and approves the requested scopes; the server redirects back with `code`.

**API key:** the user pastes the key into your configuration (`VIRALNOTE_API_KEY`). Nothing to claim.

## Exchange

**OAuth:** `POST https://dashboard.viralnote.app/api/mcp/oauth/token` with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, `code_verifier`. Response: `{ "access_token": "vnat_...", "token_type": "Bearer", "expires_in": ..., "refresh_token": "...", "scope": "..." }`. Refresh with `grant_type=refresh_token`.

**API key:** the key is the bearer credential; there is no exchange step.

## Use the access_token

Send either header on every request to `https://dashboard.viralnote.app/api/v1` or `https://dashboard.viralnote.app/api/mcp/mcp`:

```
Authorization: Bearer vnd_... | vnat_...
x-api-key: vnd_... | vnat_...
```

Each operation requires one scope (see `security` in https://www.viralnote.app/openapi.json). Reads need `posts:read`; creating drafts and importing media need `posts:write`; `publish_post` and scheduled publishing need `posts:publish`.

## Errors

| Status | `error.code` | Meaning | What to do |
|--------|---------------|---------|------------|
| 401 | `unauthorized` | Missing, malformed, revoked, or expired credential | Re-read `WWW-Authenticate`, refresh the token or ask the user for a new key |
| 403 | `unauthorized` ("Insufficient ... scope") | Credential lacks the required scope | Request the scope listed for the operation |
| 403 | `payment_required` | Account cannot publish or schedule (trial ended) | Hand the `upgrade` URL to the user; drafts and reads keep working |
| 429 | `rate-limit` | Per-key limit hit | Wait `Retry-After` seconds |
| 400 | `validation-error` | Bad payload | Fix the fields named in `message` |

All errors are JSON with a `requestId` for support.

## Revocation

- Users revoke API keys or rotate them at https://dashboard.viralnote.app/developers/auth; revoked keys fail with 401 immediately.
- OAuth connections are revoked from the dashboard's connections page; access and refresh tokens fail with 401 afterwards.
- There is no `events_endpoint` for pushed revocation notices yet; agents should treat any 401 as a signal to re-run Claim.

Questions: info@viralnote.app.

---

Site index for agents: https://www.viralnote.app/llms.txt · Structured view: https://www.viralnote.app/agent.json · API: https://www.viralnote.app/openapi.json · Auth: https://www.viralnote.app/auth.md
