Disfora Dev

Developer docs

Disfora uses standard OAuth 2.0 (authorization code + PKCE). Users sign in on Disfora, approve the scopes you ask for, and your app gets tokens to call the API as them.

Quickstart

  1. Sign in here and create an app: name, redirect URIs, and the scopes it may ask for.
  2. Send users to /oauth/authorize. They come back to your redirect URI with a code.
  3. Exchange the code at /oauth/token for an access token (15 min) and a refresh token (30 days).
  4. Call the API with Authorization: Bearer …. Refresh before the access token expires.

Scopes

Your app may only ask for scopes ticked in its settings. profile is always included.

ScopeWhat the user sees
profileSee your username, name and profile picture
emailSee your email address
comments:writePost, edit and delete your comments
ratings:writeRate pages as you
notifications:readRead your notifications

1. Send the user to Disfora

GET https://disfora.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://example.com/auth/disfora/callback   (exact match)
  &state=RANDOM_STRING             (8+ characters; check it on return)
  &scope=profile comments:write    (optional; profile is always included)
  &code_challenge=BASE64URL(SHA256(verifier))
  &code_challenge_method=S256      (PKCE: required for public apps, recommended for all)

They sign in, see your app's name and the scopes, and click Allow. They come back to redirect_uri?code=…&state=…, or ?error=access_denied if they cancel.

2. Exchange the code (within 5 minutes, once)

POST https://disfora.com/oauth/token
Content-Type: application/x-www-form-urlencoded       (JSON works too)

grant_type=authorization_code
&code=CODE
&redirect_uri=https://example.com/auth/disfora/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET    (or HTTP Basic; public apps omit it)
&code_verifier=VERIFIER
{
  "access_token": "dfa_…", "token_type": "bearer", "expires_in": 900,
  "refresh_token": "dfr_…", "scope": "profile comments:write",
  "user": {
    "sub": "stable id for this user in your app",
    "username": "…", "first_name": "…", "last_name": "…",
    "avatar_url": "…", "profile_url": "https://disfora.com/u/…",
    "email": "…", "email_verified": true      (only with the email scope)
  }
}

Key your users on sub: it never changes for your app and is different in every app. Keep tokens and the client secret on your server.

3. Call the API as the user

EndpointScope
GET /oauth/userinfoany
POST /api/comments/comments:write
PUT /api/comments/{comment_id}comments:write
DELETE /api/comments/{comment_id}comments:write
POST /api/ratings/ratings:write
GET /api/ratings/my-ratingratings:write
DELETE /api/ratings/my-ratingratings:write
GET /api/notificationsnotifications:read
GET /api/notifications/unread-countnotifications:read
POST https://disfora.com/api/comments/
Authorization: Bearer ACCESS_TOKEN
X-Project-Id: proj_…                 (the site's project key: comments and ratings)
X-Page-Origin: https://the-site.com  (the site's origin: comments and ratings)
Content-Type: application/json

{"page_url": "https://the-site.com/post", "comment": "Hello", "parent_comment_id": null}

Reading a page's comments is public: GET /api/comments/?page_url=… with the same two headers, no token needed.

4. Refresh, and revoke on sign-out

POST https://disfora.com/oauth/token
grant_type=refresh_token&refresh_token=dfr_…&client_id=…&client_secret=…

POST https://disfora.com/oauth/revoke
token=dfr_…&client_id=…&client_secret=…              (always 200)

Every refresh returns a new refresh token: store it and drop the old one. Reusing a spent refresh token revokes all of that user's tokens for your app.

Rules

  • Access tokens last 15 minutes, refresh tokens 30 days (renewed on each refresh).
  • Codes are single use, valid 5 minutes, and bound to your client, redirect URI and PKCE verifier.
  • Up to 600 requests a minute per app. Per-user limits, bans and moderation apply as normal.
  • Tokens never carry moderator powers, even for a moderator's account.
  • Users can disconnect your app from their profile; deleting your app revokes everything. Both take effect immediately.
  • Removing a scope from your app removes it from every existing token too.

Errors

Bodies are {"detail": "…"}.

  • 400 on authorize: unknown client, unregistered redirect URI, bad state, or a scope your app isn't registered for.
  • 401 on token/revoke: wrong or missing client secret.
  • 400 Invalid or expired refresh token: spent, expired or revoked.
  • 401 on the API: expired or revoked token, or an endpoint apps can't use.
  • 403 + WWW-Authenticate: Bearer error="insufficient_scope": the token lacks that endpoint's scope.
  • 429: your app's rate limit. Wait for Retry-After.