Registration and configuration Developer
Last updated Aug 16, 2026
Registration and configuration
Before you integrate, create an OAuth / OIDC client in the Developer portal (the page calls it an identifier), configure redirect URIs and scopes, and collect credentials for the client type. Protocol integration (authorization code, tokens, consent page, and so on) is covered in Integration overview and the docs that follow in this directory.
11 August 2026: Identifier scopes can be edited in the portal. Required scopes need a reason and a privacy policy. Users must confirm authorization on every sign-in. See 11 August 2026: scopes and consent platform update under Updates in this directory.
Entry
- Sign in to develop.swaymoon.com with Swaymoon Account.
- From Planned resources on the home page, open Identifiers (the sidebar has the same entry).
- Click + in the upper left to open the Register an app identifier wizard.
- Complete the wizard in order: Description → Icon → Redirect URI → Capabilities → Confirm.
The Developer portal manages clients through the Swaymoon Account API (https://api-passport.swaymoon.com/api/v1/developer/...). Sign in to Swaymoon Account before you perform these operations.
Client types
| Type | clientType | Token endpoint auth | Typical use |
|---|---|---|---|
| Confidential client | confidential (default) | client_secret_basic | Web / server apps that can keep client_secret on the backend |
| Public client | public | none (PKCE only) | Pure HTML / static sites / SPAs, native clients, and similar: runtime must not hold client_secret |
| JWT assertion client | private_key_jwt | private_key_jwt | Keep an EC P-256 private key on your server; the portal stores only the public key. Use client_assertion at the token endpoint |
All three types require PKCE (S256). The user must explicitly confirm on the authorization page before sign-in completes (prior consent is not silently reused). Client type cannot be switched after creation.
What JWT assertion is
private_key_jwt is an OAuth 2.0 / OpenID Connect client authentication method (RFC 7523). At the token endpoint, your server signs a short-lived JWT (client_assertion) with a private key you keep; Swaymoon Account verifies it with the public key registered in the portal. It is still a confidential client — the private key must not leave your server — but identity is proven with an asymmetric key instead of a shared client_secret.
| Compare | Confidential (client_secret_basic) | JWT assertion (private_key_jwt) | Public (none) |
|---|---|---|---|
| Use when | You have a backend that can keep a shared secret | You have a backend, do not want the portal to see a private key, and want rotation by kid | SPA / static / native: runtime must not hold a secret |
| Portal stores | Cannot re-show client_secret plaintext | Public key only | No secret |
| Token exchange | HTTP Basic + PKCE | client_assertion + PKCE | Form client_id + PKCE |
| Refresh Token | Yes | Yes | No |
Choose JWT assertion for server apps that should not keep a long-lived client_secret in env vars, or that need zero-downtime key rotation.
Do not choose it for a pure browser SPA or a static site that cannot keep a private key — use a public client.
Required client_assertion claims and the request shape are in Authorization code and PKCE and Examples and troubleshooting.
Wizard fields
| Step | Field | Notes |
|---|---|---|
| Description | Description, client type, public JWK | Description is the app name shown on the consent page. Types are in the table above (type is read-only when editing). JWT assertion requires an EC P-256 public JWK with a kid |
| Icon | Default icon or upload and crop | Shown on the consent page. A default can be generated from the app’s first character. You can also upload JPEG / PNG / WebP and crop to about 512×512 square JPEG (max 512KB) |
| Redirect URI | Redirect URI allowlist | Address the user’s browser returns to after success or cancel. Register at least one, and it must match redirect_uri in the authorization request exactly (you can add more). Local development may use http://127.0.0.1 |
| Redirect URI | Data deletion callback URL (optional) | Address the Swaymoon Account server POSTs a signed payload to when the user requests data deletion (not a browser redirect). See “Data deletion callback URL” below |
| Redirect URI | Privacy policy URL | Visible on the consent page and in App authorizations details. Before opening, the user is told the domain is not under Swaymoon control. Required when you request any required scope other than openid. See “Privacy policy URL” below |
| Capabilities | Required / optional scope and reasons | Configurable on create and edit. openid is always required. email and profile field scopes can be required, optional, or not requested. Required scopes need a user-visible reason and a privacy policy. See Scopes and consent |
| Confirm | — | Create with Register. Edit with Save to update name / icon / callbacks / data deletion URL / privacy policy / scopes and reasons |
After creation
Confidential client
| Field | Notes |
|---|---|
client_id | Client identifier starting with swm_. Safe to appear in authorization URLs |
client_secret | Shown only once at creation or rotation. Store it in a secrets manager immediately. If it is lost, rotate it from identifier details — you do not need to delete and recreate |
Configuration:
- Token endpoint auth:
client_secret_basic - Grant types:
authorization_code,refresh_token - Access Token about 5 minutes; Refresh Token about 180 days (the previous Refresh Token is not reused after a refresh)
Public client
| Field | Notes |
|---|---|
client_id | Client identifier starting with swm_ |
client_secret | Not issued |
Configuration:
- Token endpoint auth:
none - Grant types:
authorization_codeonly (norefresh_token; after the Access Token expires, run the authorization-code flow again) - Access Token about 5 minutes
- When exchanging the code, send
client_idandcode_verifierin the request body. Do not use HTTP Basic - Runnable walkthrough: Minimal Python example. AI-assisted: Integrate a confidential client with generative AI and Integrate a public client with generative AI
JWT assertion client
| Field | Notes |
|---|---|
client_id | Client identifier starting with swm_ |
client_secret | Not issued |
Public key kid | The EC P-256 public key registered at creation; you can add more keys from the detail page for rotation |
Configuration:
- Token endpoint auth:
private_key_jwt(client_assertion+ ES256) - Grant types:
authorization_code,refresh_token - Access Token about 5 minutes; Refresh Token about 180 days
- When exchanging the code, send
client_id,code_verifier, andclient_assertion. Do not use HTTP Basic - Keep the private key on your server; the portal rejects private JWKs
Redirect URI rules
- Provide at least one non-empty URI.
redirect_uriin the authorization request must match a registered value character for character, including scheme, host, port, path, and query.- Do not use
#fragmentin a Redirect URI (the protocol forbids it). - Production should use
https://. Local development may use loopback such ashttp://127.0.0.1:..., subject to the server’s validation policy. - Wildcard domains are not supported. Register a URI per environment.
http://127.0.0.1andhttp://localhostare different addresses.- Who visits: after authorization, the user’s browser navigates here. If you register
http://127.0.0.1:8766/callback, the login callback reaches the local demo as long as the browser and the demo are on the same computer.
Data deletion callback URL
Optional. When a user chooses Revoke and request data deletion in the Swaymoon Account privacy center, Swaymoon Account will:
- Revoke authorization for that app;
- Email an explanation to the app owner’s bound address;
- If this URL is configured, the machine running the Swaymoon Account API process
POSTs to it (signing is described in Scopes and consent).
| Item | Notes |
|---|---|
| Required? | No. If unset, only email is sent; no HTTP callback |
| Allowed URL | Only https://… reachable by the Swaymoon Account server |
| Forbidden | http:// (any), 127.0.0.1, localhost, ::1, and other loopback addresses |
| Who visits | The Passport server, not the browser (do not reuse a loopback login Redirect) |
| Local testing | Expose a local receiver as HTTPS with ngrok, Cloudflare Tunnel, or similar, and register the tunnel URL here. The tunnel and receiver must be running when the user taps delete |
| vs Redirect | Login Redirect may still use http://127.0.0.1 (browser navigation). Deletion callback and login callback are different paths |
| Signing key | Confidential: plaintext client_secret issued at creation or rotation. Public / JWT (callback configured): webhookSecret issued at creation or rotation. Verify the raw body; see Scopes and consent |
Runnable walkthrough: “Demo data deletion callback” in the confidential-client section of Minimal Python example.
Privacy policy URL
Optional. After you register it, users can view and open it from:
- The authorization consent page (if configured);
- Account Privacy → App authorizations → app details;
Before the jump, Swaymoon Account shows: This domain is not controlled by Swaymoon. Its content is not managed by Swaymoon.
| Item | Notes |
|---|---|
| Required? | Required when you request any required scope other than openid; otherwise optional |
| Allowed URL | Public https://…. Local testing may use http://127.0.0.1 / http://localhost (https:// is also fine) |
| Forbidden | http:// to a non-loopback host |
| Who visits | The user’s browser (same as login Redirect; not server egress) |
| API field | privacyPolicyUri in the create / update client request body |
Before production launch, publish an HTTPS privacy policy page so users can see how you handle their data before they authorize.
Scope recommendations
| Need | Suggested scope |
|---|---|
| Sign-in only, create a local account | openid |
| Display a name | openid + name (add nickname / picture if needed; optional or required) |
| Contact email | openid + email (required or optional in the portal; honor cancel when optional) |
| Store the username in your app | openid + preferred_username (if required, provide a reason and privacy policy) |
openid is always required. If email or a profile field is required, provide a user-visible reason and a privacy policy URL. Optional scopes are checked by default on the consent page; the user can uncheck them. See Scopes and consent.
Management
- List: view created identifiers, names,
client_id, client type and token endpoint auth, required / optional scopes. - Details: view callbacks, privacy policy, scopes, whether a webhook secret has been issued, and JWT public-key
kids. - Edit: you can change description (name), icon, redirect URIs, data deletion callback URL, privacy policy URL, required / optional scopes, and reasons for required scopes.
- Rotate secrets: confidential clients can rotate
client_secretfrom the detail page (this also updates the deletion-callback signing key). Public / JWT clients can issue or rotatewebhookSecret. - Add public key: JWT assertion clients can register another public key (
kid) from the detail page for zero-downtime rotation. - Not editable: client type (confidential / public / JWT assertion) is locked after creation. To change type, delete the old identifier, register again, and update your app.
- Delete: after deletion, issued tokens no longer work for that client, and user-side authorizations become invalid.
Compatibility: existing production clients do not need code changes because the platform now supports editable scopes and required authorization confirmation. Develop new clients against the latest docs. See Scopes and consent.
To create a client, call POST /api/v1/developer/clients with JSON including clientType, requiredScopes / optionalScopes, scopeReasons, and optional dataDeletionUri, privacyPolicyUri. To update an existing client, call PUT /api/v1/developer/clients/{clientId} (scopes and reasons can be updated).
Next: read Integration overview in this directory, then implement Authorization code and PKCE.