Edit on val.town ↗

OAuth

Val Town is an OAuth 2.0 authorization server. Instead of asking users to paste an API token, your app can send them through a standard OAuth flow and receive an access token scoped to just the permissions you request. This is also how MCP clients connect to Val Town's MCP server.

You don't need to fill out a form or email us to create an OAuth client. Registration (RFC 7591) is open — POST your client metadata and you get a client back:

curl -X POST https://www.val.town/oauth/reg \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My App",
    "redirect_uris": ["https://myapp.example.com/callback"],
    "token_endpoint_auth_method": "none"
  }'

Registration is rate limited to 10 requests per minute per IP.

Request scopes as a space-separated list. Each Val Town resource has a read (_r) or read/write (_rw) scope, mirroring API token scopes:

Scope Grants
user_r Read your user account details
project_rw Read and write vals
blob_rw Read and write blob storage
sqlite_rw Read and write your SQLite databases
email_rw Send emails
telemetry_r Read logs and traces
openid, profile, email OpenID Connect identity claims
offline_access Receive a refresh token

The scopes users grant on the consent screen become the permissions on the issued access token — an app granted only sqlite_rw cannot touch your vals or send email.

A few quirks to be aware of, for historical reasons:

Access tokens work exactly like API tokens — pass them as a Bearer token to api.val.town:

curl https://api.val.town/v1/me \
  -H "Authorization: Bearer <access_token>"

Tokens are opaque (not JWTs) and expire after 24 hours. If you requested offline_access, use your refresh token at the token endpoint to get a new one. Refresh tokens are single-use: each refresh returns a new refresh token and invalidates the old one.

Val Town supports RFC 8707 resource indicators. The default (and currently only meaningful) resource is https://api.val.town. Scopes on the issued token are intersected with the scopes the API resource server supports, so requesting a scope outside the supported set silently drops it from the token.

Val Town's MCP server at https://api.val.town/v3/mcp uses this OAuth server for authentication, following the standard MCP authorization flow:

  1. An unauthenticated request to the MCP endpoint returns 401 with a WWW-Authenticate header pointing at https://api.val.town/.well-known/oauth-protected-resource.
  2. That metadata names https://www.val.town/oauth as the authorization server and lists the supported scopes.
  3. The client registers itself via dynamic client registration, runs the authorization code + PKCE flow, and connects with the resulting token.

Any MCP client that implements the MCP authorization spec — Claude, Cursor, VS Code, and friends — handles this automatically; users just click through the consent screen.

Last updated