View .md

OAuth2 app authorization

How a third-party app asks a FeatherPanel user for an API key through the consent screen, the device flow for CLIs, callback modes and the error codes.

On this page

FeatherPanel has a consent flow so an app can ask a user for API access without the user copying keys by hand. It is OAuth2 in shape (a consent screen, an authorization code, a device flow), but what the app receives is an ordinary FeatherPanel API key. There are no scopes and no refresh tokens: the key can do what the user can do, and it lives until the user deletes it.

Browser flow

Send the user to the consent page on the panel with the request in the query string:

https://panel.example.com/dashboard/account/oauth2/api/new
  ?name=My%20Bot
  &callbackurl=https://app.example.com/featherpanel/callback
  &appName=My%20Bot
  &appLogo=https://app.example.com/logo.png
  &description=Reads%20server%20status
  &allowedips=203.0.113.10
  &mode=user
ParameterRequiredMeaning
nameyesName of the API key that will be created, up to 191 characters.
callbackurlyesWhere the result goes. See the rules below.
appNamenoApp name shown on the consent screen, up to 191 characters.
appLogonoLogo URL shown on the consent screen. Must be a valid URL.
descriptionnoStored as the key description.
allowedipsnoIP allow list for the new key, same format as the API keys page.
alertCorsnotrue turns on the foreign IP e-mail alert for the key.
modenouser (default) or server.

The user signs in if needed, sees the app name and logo, and approves or denies. A consent request stays valid for 24 hours.

Callback URL rules

  • https URLs are always allowed.
  • http is only allowed for localhost and 127.0.0.1, except in server mode.
  • Custom schemes such as myapp:// are allowed, for desktop and mobile apps.
  • javascript:, data:, file: and vbscript: are rejected with INVALID_CALLBACK_URL.

What the app receives

In user mode the browser is redirected to the callback URL with the result in the fragment, so it never reaches web server logs:

https://app.example.com/featherpanel/callback#public_key=...&private_key=...&token_type=featherpanel_api_key&issued_at=2026-10-23T08%3A00%3A00%2B00%3A00&authorization_code=fpoauthcode_...

If the user denies, the fragment carries error=access_denied instead.

In server mode the panel does not redirect. It sends a JSON POST to the callback URL with success, token_type, public_key, private_key, authorization_code, issued_at, app_name and request_name, with a 15 second timeout. If that delivery fails, the user sees SERVER_CALLBACK_DELIVERY_FAILED (502).

Exchange the authorization code

The callback also carries an authorization_code. A backend that only received the code can trade it for the key once:

curl -X POST https://panel.example.com/api/user/api-clients/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{"code":"fpoauthcode_..."}'

The response data holds token_type, public_key and private_key. A second exchange of the same code returns AUTHORIZATION_CODE_USED; an unknown or expired code returns INVALID_AUTHORIZATION_CODE.

Device flow

For CLIs and devices without a browser, start a device authorization:

curl -X POST https://panel.example.com/api/user/api-clients/oauth2/device \
  -H "Content-Type: application/json" \
  -d '{"name":"My CLI","appName":"My CLI"}'

The body accepts name (required), appName, appLogo, description, allowedips and alertCors. The response contains:

FieldMeaning
device_codeSecret the app polls with.
user_codeShort code the user types in.
verification_uri/dashboard/account/oauth2/api/device on your panel.
verification_uri_completeThe same page with the code filled in.
expires_in900 seconds.
interval5 seconds between polls.

Show the user the user_code and the link, then poll:

curl -X POST https://panel.example.com/api/user/api-clients/oauth2/device/token \
  -H "Content-Type: application/json" \
  -d '{"device_code":"fpdev_..."}'
ResultMeaning
authorization_pendingThe user has not answered yet. Keep polling.
slow_downYou polled faster than every 5 seconds.
access_deniedThe user denied the request.
expired_tokenThe code expired. Start again.
successdata has public_key, private_key and issued_at.

Revoking access

Every approved request creates an API key named after the request on the user’s account, and the approval is recorded in the account activity. The user removes access by deleting that key on the API keys page.

Frequently asked questions

Does FeatherPanel issue OAuth access tokens?

No. When the user approves, the panel creates a normal API key (public and private key) on their account and hands it to the app. The app then sends the private key as a Bearer token.

How long does a device code stay valid?

900 seconds. Poll the token endpoint no faster than every 5 seconds or the panel answers slow_down.

Can the user revoke an app later?

Yes. The approved app shows up as an API key on the user's account. Deleting that key revokes the app.