OAuth app
Build an app that users authorize with their own accounts — client registration, authorization with PKCE, tokens, and refresh.
Every Solk installation runs an OAuth 2.1 authorization server. The same server issues tokens to MCP clients (Claude) and to apps that call the REST API on behalf of users. The supported flows are authorization code + PKCE (S256) and refresh token; the password flow and the client credentials flow are not supported.
| URL | |
|---|---|
| Discovery | https://ornek.solk.app/.well-known/oauth-authorization-server |
| Client registration | POST https://ornek.solk.app/oauth/register |
| Authorization | GET https://ornek.solk.app/oauth/authorize |
| Token | POST https://ornek.solk.app/oauth/token |
| Revocation | POST https://ornek.solk.app/oauth/revoke |
Each installation has its own authorization server: get the user's installation address (for example, ornek.solk.app) from the user and read the discovery document from that address.
- 1
Register the client
Register your app once (Dynamic client registration, RFC 7591). Server-side apps that can keep a secret choose
client_secret_post; desktop and mobile apps usenone(public client).curl https://ornek.solk.app/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "Örnek Entegrasyon", "redirect_uris": ["https://uygulamaniz.com/oauth/callback"], "token_endpoint_auth_method": "none" }'{ "client_id": "crm_Jq8w…", "client_id_issued_at": 1790939471, "client_name": "Örnek Entegrasyon", "redirect_uris": ["https://uygulamaniz.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"] }Redirect URIs must use
https://or a loopback address (http://localhost,http://127.0.0.1,http://[::1]). For loopback addresses, the port number is ignored during matching. Instead of client registration, you can also use CIMD: pass thehttps://URL of your client metadata document as theclient_id. - 2
Generate PKCE values
For each authorization, generate a random
code_verifier(43–128 characters) and acode_challenge, which is the base64url-encoded SHA-256 hash of the verifier:import secrets, hashlib, base64 verifier = secrets.token_urlsafe(48) challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") - 3
Redirect the user
https://ornek.solk.app/oauth/authorize ?response_type=code &client_id=crm_Jq8w… &redirect_uri=https%3A%2F%2Fuygulamaniz.com%2Foauth%2Fcallback &scope=crm.read%20crm.write &state=xyz123 &code_challenge=E9Melhoa2Owv… &code_challenge_method=S256If the user isn't signed in, the sign-in screen appears first. The consent screen shows your app's name, the domain of the redirect URI, and the requested scopes. If the user clicks Allow (İzin ver), the browser returns to:
https://uygulamaniz.com/oauth/callback?code=Zx8…&state=xyz123If the user declines, you get
?error=access_denied&state=xyz123. Verify that thestatevalue matches the one you sent. - 4
Exchange the code for tokens
The code is valid for 5 minutes and can be used once. The token endpoint expects a form body:
curl https://ornek.solk.app/oauth/token \ -d grant_type=authorization_code \ -d code=Zx8… \ -d redirect_uri=https://uygulamaniz.com/oauth/callback \ -d client_id=crm_Jq8w… \ -d code_verifier=$VERIFIER{ "access_token": "mcp_uWrPBo…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "mcpr_Nu5ue…", "scope": "crm.read crm.write" } - 5
Call the API
curl https://ornek.solk.app/api/v1/me -H "Authorization: Bearer mcp_uWrPBo…"The access token is valid for the REST API and the MCP endpoint. Without the
crm.writescope, write requests return403 insufficient_scope. - 6
Refresh the token
The access token expires after 1 hour. Use the refresh token to get a new pair — every refresh returns a new refresh token and invalidates the old one:
curl https://ornek.solk.app/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=mcpr_Nu5ue… \ -d client_id=crm_Jq8w…Retrying with an old refresh token returns
400 invalid_grant. A refresh token expires if it goes unused for 60 days; the user must then grant access again.
Grants and revocation
- When a user authorizes the same app a second time, the existing grant is updated (its scope is replaced by the scope of the new consent); each app has a single grant per user.
- In Settings → App connections (Ayarlar → Uygulama bağlantıları), users see your app, its last-used time, and its call count; Remove access (Erişimi kaldır) immediately revokes every token issued under that grant.
- If the user is deactivated, their tokens stop working.
- Your app can revoke a token itself by calling
POST /oauth/revoke.
Errors
| Situation | Response |
|---|---|
The code expired, was already used, belongs to a different client, or the redirect_uri doesn't match |
400 {"error": "invalid_grant"} |
| PKCE verification failed | 400 {"error": "invalid_grant", "error_description": "PKCE doğrulanamadı."} |
| The same code was used a second time | 400 invalid_grant and every token issued under that grant is revoked |
| Unsupported grant type | 400 {"error": "unsupported_grant_type"} |
| Too many requests | 429 {"error": "slow_down"} |