Authorization
OAuth 2.0 Applications, tokens and integration flow for Speaknode API
The Speaknode API uses OAuth 2.0 (Authorization
Code + PKCE) on top of a Keycloak realm. To call the API on behalf of a
Speaknode user from your own application, register an
OAuth Application, run the standard Authorization Code flow against
the identity server, and use the returned access_token as a Bearer
token.
Quick token for one-off
curl/ Postman calls — openhttps://app.speaknode.com/tokenwhile signed in. The page shows the current access token, no app registration required.
1. Register your OAuth Application
Sign in to Speaknode and open https://app.speaknode.com/developers → OAuth Applications → Create OAuth app:
- Name — anything human-readable (e.g.
My CRM Integration). - Redirect URIs — exact URLs your app will receive the auth code on
(e.g.
https://my-crm.example.com/oauth/callback). At least one is required; multiple are allowed for staging/prod. - Type:
- Public — for browser/SPA/mobile apps that cannot keep a secret. PKCE is mandatory.
- Confidential — for server-side apps. You receive a
client_secretonce on creation and must store it securely.
After creation the UI shows:
{
"client_id": "8f3a1c2e-...-...",
"client_secret": "xPq..."
}The client_secret is shown only once — copy it immediately. If lost,
rotate it from the application's settings page.
The OAuth endpoints (auth_url, token_url, etc.) are the same for every
application on the platform — see §2 below.
2. Endpoints
Base URL: https://identity.speaknode.com/realms/speaknode
| Purpose | Path |
|---|---|
| OIDC discovery | /.well-known/openid-configuration |
| Authorize | /protocol/openid-connect/auth |
| Token | /protocol/openid-connect/token |
| UserInfo | /protocol/openid-connect/userinfo |
| Logout | /protocol/openid-connect/logout |
| JWKS (signature keys) | /protocol/openid-connect/certs |
3. Authorization Code flow with PKCE
Step 1 — generate a PKCE pair
code_verifier = random url-safe string (43–128 chars)
code_challenge = BASE64URL(SHA256(code_verifier))Keep code_verifier server-side / in memory until step 3.
Step 2 — redirect the user to authorize
Send the user's browser to:
GET https://identity.speaknode.com/realms/speaknode/protocol/openid-connect/auth
?client_id=<your-client-id>
&redirect_uri=https://my-crm.example.com/oauth/callback
&response_type=code
&scope=openid profile email offline_access
&code_challenge=<challenge>
&code_challenge_method=S256
&state=<csrf-random>- Include
offline_accessinscopeif you want a long-livedrefresh_token(see §5). - Optional:
&kc_locale=ruto force the login UI / emails to a specific language.
The user signs in to their Speaknode account and Keycloak redirects to your callback:
https://my-crm.example.com/oauth/callback?code=<code>&state=<state>Validate that state matches the value you sent.
Step 3 — exchange the code for tokens
POST https://identity.speaknode.com/realms/speaknode/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=<your-client-id>
&client_secret=<your-client-secret> # confidential clients only
&code=<code>
&redirect_uri=https://my-crm.example.com/oauth/callback
&code_verifier=<verifier>Response:
{
"access_token": "<jwt>",
"expires_in": 86400,
"refresh_token": "<jwt>",
"refresh_expires_in": 2592000,
"id_token": "<jwt>",
"token_type": "Bearer",
"scope": "openid profile email offline_access"
}Step 4 — call the API
curl -X GET https://api.speaknode.com/api/Users/me \
-H "Authorization: Bearer <access_token>"The backend validates the JWT signature against the JWKS endpoint and
checks iss, aud, exp.
4. Refreshing the access token
access_token lives 24 hours. Before it expires (or on a 401), exchange
the refresh_token:
POST https://identity.speaknode.com/realms/speaknode/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&client_id=<your-client-id>
&client_secret=<your-client-secret> # confidential clients only
&refresh_token=<refresh_token>Returns a new access_token plus a new refresh_token (rotated).
5. Token lifetimes
| Token | Lifetime | Notes |
|---|---|---|
access_token | 24h | Use as Bearer to call https://api.speaknode.com |
| Browser SSO session | 24h idle / 24h max | Affects the user's login session in Speaknode, not your refresh_token |
refresh_token (offline) | 30 days idle, no hard cap | Granted only when scope contains offline_access. Survives the user logging out of Speaknode. Refresh at least once every 30 days. |
If you do not request offline_access, your refresh_token is tied to
the user's SSO session — when they log out of Speaknode,
your refresh stops working. For background integrations always include
offline_access.
6. Logout
Revoke the user's session and the refresh token:
POST https://identity.speaknode.com/realms/speaknode/protocol/openid-connect/logout
Content-Type: application/x-www-form-urlencoded
client_id=<your-client-id>
&client_secret=<your-client-secret> # confidential clients only
&refresh_token=<refresh_token>7. Security checklist
- Public clients must use PKCE. Never embed a
client_secretin a browser, mobile app or any binary you ship to users. - Confidential clients must keep the secret server-side. Rotate it from the application settings page if exposed.
- Always validate
stateon the callback to prevent CSRF. - Validate
id_token/access_tokenagainst the JWKS endpoint (/protocol/openid-connect/certs) — never trust the JWT body without signature verification. - Restrict
redirect_urito exact values; avoid wildcards.