Troubleshooting
Integrations — Troubleshooting
The most common integration failures, and the one-line fix. For the platform's standard error envelope, see Errors.
401 Unauthorized
The credential is missing, malformed, revoked, or expired. The exact message tells you which:
| Message | Cause | Fix |
|---|---|---|
Missing API key / Missing access token | No credential on the request | Send X-Api-Key: crtk_live_… or Authorization: Bearer crtk_live_… / crtk_oauth_… |
Invalid or expired API key | Unknown, revoked, or past expires_at | The customer revoked it or it lapsed — reconnect with a fresh key |
Invalid or revoked access token | OAuth token unknown or revoked | Re-run the OAuth flow (or refresh) |
Access token expired | OAuth access token past its ~1h TTL | Use the refresh_token grant to mint a new one |
Token is not bound to a workspace | A hyper token with no workspace | The credential is malformed — re-issue it |
Treat a sudden 401 as "disconnected": clear the stored credential and prompt to reconnect. A revoked key or a revoked OAuth grant stops working on the very next call.
403 Forbidden
Authenticated, but the credential lacks the scope for that route. Scopes are
route-name strings (read.contacts, create.deals, …).
- Records: reading needs
read.{objectSlug}, writing needscreate/update/delete.{objectSlug}. Check the granted scopes in the/meresponse and confirm the object slug (schemas are customizable). - Public webhooks: managing subscriptions requires an API key or OAuth
token — a human (JWT) caller is rejected with "Public webhooks must be
managed with an API key or an OAuth token". Creating a subscription also needs
read.{object_slug}— a webhook can only carry data the key could read. - Minting a key with a scope the creator doesn't hold → "You cannot grant scopes you don't hold: …". Grant only scopes the creating admin has (super-admins may grant any).
429 Too Many Requests
"API key rate limit exceeded" — the per-key fixed-window limiter
(API_KEY_RATE_LIMIT_PER_MINUTE, default 120/min) tripped. Slow down, batch
requests, or raise the limit. It applies to API-key traffic only, never human
JWTs. (A Redis blip fails open — the limiter never locks out a valid key.)
OAuth token exchange errors
POST /oauth/token returns raw RFC 6749 JSON with a specific message:
| Message | Meaning & fix |
|---|---|
code and redirect_uri are required (400) | Include both in the authorization_code body |
invalid_grant: code is invalid, expired, or already used | Codes are single-use and live 60s — exchange immediately |
invalid_grant: redirect_uri mismatch | The exchange redirect_uri must byte-match the one in the authorize request and the registered URI |
invalid_grant: client_id mismatch | The client_id differs from the one the code was issued to |
invalid_grant: PKCE required for public clients | Public clients must send code_challenge (authorize) + code_verifier (token) |
invalid_grant: PKCE verification failed | The code_verifier doesn't match the code_challenge (S256) |
invalid_client: bad client secret | Confidential client — wrong/missing client_secret |
invalid_client | Unknown/inactive client_id |
invalid_grant: refresh token / refresh token expired | Refresh token unknown, already rotated, or past 30d — reconnect. Note: presenting an already-rotated refresh token is treated as a leak and revokes the whole token family |
Consent screen errors (GET/POST /oauth/authorize)
400on the GET — unknown/inactiveclient_id,redirect_urinot an exact match of a registered URI, a requested scope the app isn't allowed, PKCE missing for a public client, orresponse_type≠code. Show an error screen; never redirect the browser (the backend withholdsredirect_toas an open-redirect defense).400on the POST — approving when none of the requested scopes are grantable by this user's permissions ("None of the requested scopes can be granted by your account"). The user needs the underlying object permissions.401— the user isn't logged in; send them to login and return to the exact consent URL with all query params preserved.
The call succeeds but returns fewer rows than expected
Scopes are only the coarse endpoint gate. Past it, every record query is
re-filtered by the acting admin's live A/G/M/D level — a key that acts as an
admin with M (mine) access sees only that admin's records, not the whole object.
Raise the admin's access level or have the key act as an admin with broader
rights. See Authorization.
Inbound webhooks never arrive
A subscription delivers a signed POST to your target_url (which must be
HTTPS). If nothing arrives: confirm the subscription is active, that your
endpoint returns 2xx quickly, and that you're verifying X-Corteksa-Signature
correctly. Remember an event caused by your own key's write is intentionally
never echoed back. See Webhooks.
Next
- Avoid these in the first place → Best Practices
- Every endpoint → REST API