REST API

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. 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 use none (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 the https:// URL of your client metadata document as the client_id.

  2. 2

    Generate PKCE values

    For each authorization, generate a random code_verifier (43–128 characters) and a code_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. 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=S256

    If 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=xyz123

    If the user declines, you get ?error=access_denied&state=xyz123. Verify that the state value matches the one you sent.

  4. 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. 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.write scope, write requests return 403 insufficient_scope.

  6. 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"}