> ## Documentation Index
> Fetch the complete documentation index at: https://www.dropfans.io/developers/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth apps

> Register an OAuth app, send creators, fans or agencies through the authorization-code flow with PKCE, and keep their tokens fresh with rotating refresh tokens.

- Source: https://www.dropfans.io/developers/api-v1/oauth-apps
- Section: API v1 (OAuth apps)
- OpenAPI: https://www.dropfans.io/developers/openapi.json

API v1 at `https://api.dropfans.io` is authorized with OAuth 2.0 access tokens instead of API keys: the account (a creator, a fan or an agency) signs in on Dropfans, approves the scopes your app asks for, and your app receives a token that acts for that account. The authorization server is `https://auth.dropfans.io`, with OpenID Connect on top.

## Register your app

1. [Apply for API access](https://www.dropfans.io/developers/apply) and wait for the app to be approved.
2. Ask the Dropfans team for an OAuth client for that app, with the redirect URIs you will use (exact https URLs; `http://localhost` is fine for development) and the scopes it needs.
3. You receive a **client id** and a **client secret**. The secret is shown once: store it server-side only. If it leaks, ask for a new one; the old one stops working immediately.

Each API category of your app (chat, posts, vault, …) has its own switch on our side, so a scope you were granted can still answer 404 while its category is off for your app.

## Endpoints

| Endpoint | Purpose |
| --- | --- |
| `GET https://auth.dropfans.io/oauth2/auth` | Authorization: the account signs in and approves your scopes |
| `POST https://auth.dropfans.io/oauth2/token` | Exchange a code, or refresh a token |
| `POST https://auth.dropfans.io/oauth2/revoke` | Revoke an access or refresh token (and its whole grant) |
| `GET https://auth.dropfans.io/userinfo` | OpenID Connect claims of the token’s account |
| `GET https://auth.dropfans.io/.well-known/openid-configuration` | Discovery document |
| `GET https://auth.dropfans.io/.well-known/jwks.json` | Keys that sign the `id_token` (RS256) |
| `GET https://auth.dropfans.io/oauth2/sessions/logout` | End-session endpoint |

Read the endpoints from discovery rather than hard-coding them where your OAuth library supports it.

## 1. Send the account to the authorization endpoint

PKCE with **S256 is required** for every client: create a random `code_verifier`, send `code_challenge = BASE64URL(SHA256(code_verifier))`. Requests without it, or with `plain`, are refused.

```http
GET https://auth.dropfans.io/oauth2/auth?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=openid%20offline_access%20read%3Aself%20read%3Achat
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
```

`redirect_uri` must match a registered URI exactly. `scope` is space-separated; scopes your client is not allowed, or that the account type cannot hold, are left out of the grant (check `scope` in the token response). The account comes back to your `redirect_uri` with `code` and your `state`, or with `error` and `error_description`.

The code is single-use and expires after 60 seconds. Presenting a code twice revokes every token it produced.

## 2. Exchange the code

Authenticate the client with HTTP Basic (`client_secret_basic`: `client_id:client_secret`, base64). It is the only client authentication method.

```bash
curl -X POST https://auth.dropfans.io/oauth2/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier="$CODE_VERIFIER"
```

```json
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid offline_access read:self read:chat",
  "refresh_token": "…",
  "id_token": "eyJ…"
}
```

`refresh_token` is only issued when the grant includes `offline_access` (or `offline`); `id_token` only with `openid`. Its `sub` is the account’s UUID, the same `uuid` that [`GET /users/me`](https://www.dropfans.io/developers/api-v1/resources.md) returns.

## 3. Call the API

```bash
curl https://api.dropfans.io/v1/users/me -H "Authorization: Bearer $ACCESS_TOKEN"
```

See [API v1 overview](https://www.dropfans.io/developers/api-v1/overview.md) for paging, versions, errors and limits.

## 4. Refresh

```bash
curl -X POST https://auth.dropfans.io/oauth2/token -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=refresh_token -d refresh_token="$REFRESH_TOKEN"
```

Refresh tokens **rotate**: every refresh returns a new access token and a new refresh token, and the one you sent stops working. Store the new pair before using it. If the same refresh token is presented again within 60 seconds (a retried request), you get the same new pair back; after that, reuse is treated as theft and **the whole grant is revoked**. The account then has to authorize your app again.

## Token lifetimes

| Token | Lifetime |
| --- | --- |
| Authorization code | 60 seconds, single use |
| Access token | 60 minutes |
| Refresh token | 30 days from issue; each refresh starts a new one |
| `id_token` | 60 minutes |

## Revoke

```bash
curl -X POST https://auth.dropfans.io/oauth2/revoke -u "$CLIENT_ID:$CLIENT_SECRET" -d token="$TOKEN"
```

Revoking either token ends the grant. Webhook deliveries for that account stop as soon as no live grant of your app is left. Accounts can also disconnect your app themselves.

## Userinfo

`GET /userinfo` with the access token returns `sub` (the account UUID), plus `preferred_username`, `name`, `picture`, `email` and `email_verified` when the grant includes `read:self`.

## Scopes

System scopes: `openid`, `offline_access`, `offline` (`openid` adds an `id_token`; `offline_access` or `offline` adds a refresh token). API scopes, and which account types can hold them:

| Scope | Grants | Fan | Creator | Agency |
| --- | --- | --- | --- | --- |
| `read:self` | Read your profile and account details | ✓ | ✓ | ✓ |
| `read:chat` | Read your chats and messages | ✓ | ✓ | ✓ |
| `write:chat` | Send messages and manage chats | ✓ | ✓ | ✓ |
| `read:experience` | Read fan-facing experiences (not served yet) | ✓ | ✓ | ✓ |
| `write:experience` | Publish or unpublish fan-facing experiences (not served yet) | — | ✓ | ✓ |
| `read:fan` | Read your fans and their activity | — | ✓ | ✓ |
| `read:post` | Read your posts, comments and likes | — | ✓ | ✓ |
| `write:post` | Create, edit and delete posts | — | ✓ | ✓ |
| `read:media` | Read your vault media | — | ✓ | ✓ |
| `write:media` | Upload and manage vault media and folders | — | ✓ | ✓ |
| `read:creator` | Read creator profiles and the creators you manage | — | ✓ | ✓ |
| `write:creator` | Change creator profile and settings | — | ✓ | ✓ |
| `read:insights` | Read earnings and audience insights | — | ✓ | ✓ |
| `read:tracking_links` | Read tracking links and who joined through them | — | ✓ | ✓ |
| `write:tracking_links` | Create and delete tracking links | — | ✓ | ✓ |
| `read:agency` | Read your agency and its team | — | — | ✓ |
| `write:agency` | Manage agency invites and team members | — | — | ✓ |

Each API v1 operation needs every scope listed for it in the [resource list](https://www.dropfans.io/developers/api-v1/resources.md); a token without them gets `403 {"error":"Insufficient scopes"}`.

## Errors

The authorization and token endpoints answer in the standard OAuth form `{"error": "invalid_grant", "error_description": "…"}` (`invalid_request`, `invalid_client`, `invalid_grant`, `invalid_scope`, `unsupported_grant_type`, `access_denied`, …). The token endpoint allows 120 requests a minute per client and 60 per IP address.

---

Previous: [Agent prompt](https://www.dropfans.io/developers/build-with-ai/agent-prompt.md) · Next: [API v1 overview](https://www.dropfans.io/developers/api-v1/overview.md) · All pages: [llms.txt](https://www.dropfans.io/developers/llms.txt)
