Developers / OAuth
OAuth 2.0 for CLYK
Ship third-party apps that act on behalf of CLYK users. Dynamic client registration, authorization_code flow, PKCE-only, scoped bearer tokens. No client secrets to leak.
When to use OAuth
Use OAuth when your app acts on behalf of a CLYK user who is not you. If you are building a script against your own account, skip OAuth — just mint a personal API key from your dashboard.
OAuth is what you want when you are building a SaaS product, a public agent, or any tool where the end user and the developer are different people. The user approves exactly the scopes they want; you get a token scoped to those permissions and no more.
Discovery lives at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, so OAuth-aware clients can auto-wire their config.
1. Register a client
Unauthenticated. One-time per app. Returns a client_id you embed in your app.
curl -X POST https://clyk.app/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My AI Agent",
"redirect_uris": ["https://myagent.example.com/callback"]
}'{
"client_id": "clyk_oauth_XXXXXXXX",
"client_name": "My AI Agent",
"redirect_uris": ["https://myagent.example.com/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code"],
"response_types": ["code"]
}We do not issue a client_secret. All clients MUST use PKCE (RFC 7636) with S256.
2. Send the user to /oauth/authorize
Your app generates a PKCE code_verifier, derives the code_challenge, and sends the user to the URL below.
https://clyk.app/oauth/authorize
?response_type=code
&client_id=clyk_oauth_XXXXXXXX
&redirect_uri=https://myagent.example.com/callback
&scope=links:read+links:write+stats:read
&state=random-per-request-value
&code_challenge=BASE64URL(SHA256(code_verifier))
&code_challenge_method=S256If the user is not signed in we route them through /auth/signin first and then back to the consent screen. On approve we redirect to your redirect_uri with ?code=...&state=.... On deny we redirect with ?error=access_denied.
3. Exchange the code for a bearer token
Post to /oauth/token as application/x-www-form-urlencoded. Codes are single-use, expire in 10 minutes, and only redeem from the same redirect URI that received them.
curl -X POST https://clyk.app/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=<code from redirect>" \
--data-urlencode "redirect_uri=https://myagent.example.com/callback" \
--data-urlencode "client_id=clyk_oauth_XXXXXXXX" \
--data-urlencode "code_verifier=<the original verifier>"{
"access_token": "clyk_live_...",
"token_type": "Bearer",
"scope": "links:read links:write stats:read",
"refresh_token": null
}The access_token is a normal CLYK bearer token — drop it straight into Authorization: Bearer <token> on any /api/v1 or /api/mcp call.
4. Call the API
curl https://clyk.app/api/v1/me \
-H "Authorization: Bearer <access_token>"All rate-limit, auth, and logging behavior is identical to dashboard-minted keys. Tokens can be revoked at any time from your API settings.
Scopes
Request only what you need. Unknown scopes are rejected at /oauth/authorize with invalid_scope.
links:readRead your short links and their metadata.
links:writeCreate, update, and delete your short links.
stats:readRead click analytics for your links.
bio:readRead your bio page configuration.
bio:writeModify your bio page content and theme.
mcp:toolsCall tools on the CLYK MCP server.
sponsorships:readRead your sponsorship tracker.
sponsorships:writeModify your sponsorship tracker.
subscribers:readRead your email subscribers list.
Error shape
Errors follow RFC 6749 §5.2 — { error, error_description }. The error code is stable and safe to key on. Common codes: invalid_request, invalid_grant, invalid_scope, unsupported_grant_type, access_denied.
For redirect-based errors (at /oauth/authorize) the same codes come back on the redirect_uri as query parameters, along with the original state.