Authorization Flow
Register an app, drive a user through authorize and consent, and exchange the code for tokens.
Overview
The OAuth Apps flow has four steps:
- Register your app (once) → get
client_id+client_secret. - Authorize — show the connecting user what your app is requesting.
- Consent — the org admin approves; you receive a single-use
code. - Token exchange — swap the
code(with your client secret) for an access token + refresh token.
All endpoints below are on https://api.featureos.app.
1. Register an app
Owner-org admin only. Authenticated with the user's session JWT + the X-Organization header.
POST https://api.featureos.app/oauth/apps
curl -sX POST https://api.featureos.app/oauth/apps \
-H "Authorization: Bearer <USER_JWT>" \
-H "X-Organization: your-org" \
-H "Content-Type: application/json" \
-d '{
"name": "My Integration",
"redirect_uris": ["https://myapp.com/callback"],
"allowed_actor_modes": ["app", "self"],
"declared_scopes": ["posts:read", "posts:write"]
}'Each entry in redirect_uris must be an absolute https URI. http is accepted only for loopback hosts (localhost, 127.0.0.1, [::1]) to support local development.
The response includes the client_id and the client_secret (shown once).
2. Authorize
The connecting user (logged in to FeatureOS) is sent through the authorize endpoint, which returns the consent metadata (app details, requested scopes, and which orgs the user may install into).
GET https://api.featureos.app/oauth/authorize
curl -sG https://api.featureos.app/oauth/authorize \
-H "Authorization: Bearer <USER_JWT>" \
-H "X-Organization: target-org" \
--data-urlencode "client_id=foapp_…" \
--data-urlencode "redirect_uri=https://myapp.com/callback" \
--data-urlencode "scope=posts:read posts:write" \
--data-urlencode "actor=self"| Parameter | Required | Description |
|---|---|---|
| client_id | true | Your app's foapp_… id. |
| redirect_uri | true | Must exactly match one of the app's registered redirect_uris. |
| scope | false | Space-separated scopes; must be a subset of the app's declared_scopes. Omit to request all declared. |
| actor | false | app or self; must be one the app allows. |
3. Consent
The org admin approves the install into a specific target organization. This mints a single-use authorization code.
POST https://api.featureos.app/oauth/authorize/consent
curl -sX POST https://api.featureos.app/oauth/authorize/consent \
-H "Authorization: Bearer <USER_JWT>" \
-H "X-Organization: target-org" \
-H "Content-Type: application/json" \
-d '{
"client_id": "foapp_…",
"redirect_uri": "https://myapp.com/callback",
"scope": "posts:read posts:write",
"actor": "self",
"target_organization_id": 7,
"state": "xyz123"
}'
# → { "redirect_to": "https://myapp.com/callback?code=fooc_…&state=xyz123" }| Parameter | Required | Description |
|---|---|---|
| target_organization_id | true | The org to install into. The caller must be an admin of it. |
| state | false | Opaque value echoed back on the redirect (CSRF protection). |
| granted_webhook_events | false | Restrict which webhook events this install grants (subset of the app's subscribed events). See Webhooks. |
The browser is redirected to your redirect_uri with code (and state). The code is single-use and expires in 5 minutes.
4. Token exchange
Server-to-server, authenticated with your client credentials (no user session).
POST https://api.featureos.app/oauth/token
curl -sX POST https://api.featureos.app/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "foapp_…",
"client_secret": "fosec_…",
"code": "fooc_…",
"redirect_uri": "https://myapp.com/callback"
}'
# → {
# "access_token": "foot_…",
# "refresh_token": "foor_…",
# "token_type": "Bearer",
# "expires_in": 86400,
# "scope": "posts:read posts:write"
# }The exchange verifies your client secret, that the code is unused/unexpired, and that the redirect_uri matches exactly. You now have a 24-hour access token — see Using Access Tokens and Refresh.