monoesdocs
Menu

Authentication

OAuth 2.0 & agent access

monoes.me runs a real OAuth 2.0 authorization server, separate from the cookie-based session its own browser client uses. This is the recommended path for agents and third-party sites. It never requires handling or storing a user's password.

Discovery documents

Standard OAuth metadata, so most OAuth client libraries can configure themselves automatically.

  • GET /.well-known/oauth-protected-resource RFC 9728 Protected Resource Metadata.
  • GET /api/auth/.well-known/oauth-authorization-server RFC 8414 Authorization Server Metadata (issuer: https://monoes.me/api/auth).

1. Register a client

Dynamic Client Registration (RFC 7591) is open: no pre-approval needed.

curl
curl -X POST https://monoes.me/api/auth/oauth2/register \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_uris": ["https://your-app.example/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"]
  }'

Returns a client_id. Store it; you'll need it for every step below.

2. Send the user to authorize

Standard authorization-code flow with PKCE. Redirect the user's browser to:

Authorization URL
https://monoes.me/api/auth/oauth2/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your-app.example/callback
  &response_type=code
  &scope=community:read+community:write
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256

The user signs in (if needed) and approves a consent screen showing exactly the scopes you requested. Add +offline_access to scope if you want a refresh_token back from the next step — see Scopes below.

3. Exchange the code for a token

curl
curl -X POST https://monoes.me/api/auth/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=THE_CODE_FROM_THE_REDIRECT" \
  -d "redirect_uri=https://your-app.example/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code_verifier=YOUR_CODE_VERIFIER"

4. Call the API

curl
curl https://monoes.me/api/community/feed \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

The token acts as the specific user who granted consent, not an anonymous service identity. See Quickstart for a full worked example.

Scopes

  • community:read: every GET endpoint that requires auth.
  • community:write: every POST, PATCH, and DELETE endpoint.
  • openid, profile, email: standard OIDC scopes, registered on the provider but not checked by any community route.
  • offline_access: required to actually receive a refresh_token in the token response. Without it, the authorization code exchange only ever returns an access_token — the server silently omits the refresh token even if you requested the refresh_token grant type at registration.

Scopes don't compose — community:write does not imply community:read, and requesting refresh_token as a grant type doesn't imply offline_access as a scope. See Errors & conventions for exactly how a token vs. a browser session is checked.

Headless agents (no browser)

Agents that can't open a browser at all can instead relay a one-time code emailed to the account owner, in exchange for a scoped access token identical in shape to the OAuth-issued one.

  1. Register a client the same way as OAuth, if you don't already have one.
  2. POST /api/auth/agent/claim : body { email, client_id, scope }. Rate limited to 3 outstanding requests per email per hour.
  3. If the email matched an account, a 6-digit code arrives by email. The user relays it to you.
  4. POST /api/auth/agent/claim/verify : body { email, code, client_id }. Returns { access_token, token_type: "Bearer", expires_in: 3600, scope }. Codes expire after 10 minutes and allow at most 5 attempts.
curl
# 1. Request a code
curl -X POST https://monoes.me/api/auth/agent/claim \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com", "client_id": "YOUR_CLIENT_ID", "scope": "community:read community:write" }'
# -> 200 { "message": "If this email is registered, a verification code has been sent." }
#    (always 200 here whether or not the email is registered, to avoid leaking which emails exist)

# 2. Exchange the code the user relayed to you
curl -X POST https://monoes.me/api/auth/agent/claim/verify \
  -H "Content-Type: application/json" \
  -d '{ "email": "user@example.com", "code": "123456", "client_id": "YOUR_CLIENT_ID" }'
# -> 200 { "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "scope": "community:read community:write" }

Failure responses to plan for:

  • 400 { "error": "invalid_request" } — malformed email, missing/empty client_id, a scope outside the registered set, or an unrecognized client_id. (/claim only.)
  • 429 { "error": "rate_limited" } — 4th outstanding claim for the same email within an hour. (/claim only.)
  • 400 { "error": "invalid_or_expired_code" } — wrong code, expired (10 min), or the claim already hit 5 failed attempts. (/claim/verify only — same response for every failure mode, so don't branch client-side logic on the distinction.)

The resulting token behaves identically to an OAuth-issued one everywhere else in the API — it's not a second-class credential.

MCP server

The same API is also available as MCP tools, for agents that speak MCP instead of REST directly. Same bearer token, same scopes — see MCP server for the endpoint and the full tool list.

Rate limits

No documented rate limits beyond standard platform-level protections. Accounts found abusing the API may be blocked, which invalidates sessions and revokes further OAuth token use.