Skip to main content

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.

Request
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"]
  }'
Response
{
  "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.

Authorize URL
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=S256

If 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.

Request
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>"
Response
{
  "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:read

    Read your short links and their metadata.

  • links:write

    Create, update, and delete your short links.

  • stats:read

    Read click analytics for your links.

  • bio:read

    Read your bio page configuration.

  • bio:write

    Modify your bio page content and theme.

  • mcp:tools

    Call tools on the CLYK MCP server.

  • sponsorships:read

    Read your sponsorship tracker.

  • sponsorships:write

    Modify your sponsorship tracker.

  • subscribers:read

    Read 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.