1. Guides
  2. Integrations
  3. Troubleshooting

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:

MessageCauseFix
UnauthorizedNo credential on the request, or a token that is not a crtk_live_ key or crtk_oauth_ tokenSend X-Api-Key: crtk_live_… or Authorization: Bearer crtk_live_… / crtk_oauth_…
Invalid or expired API keyUnknown, revoked, or past expires_at — or sent to another workspace's subdomainThe customer revoked it or it lapsed — reconnect with a fresh key. Check the host
Invalid or revoked access tokenOAuth token unknown or revokedRe-run the OAuth flow (or refresh)
Access token expiredOAuth access token older than 1 hourUse the refresh_token grant to mint a new one
Token is not bound to a workspaceThe credential has no workspaceCreate a new key or reconnect

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, …). The message names the missing one: "Access denied. Required permission: read.contacts".

  • Records: reading needs read.{objectSlug}, writing needs create/update/delete.{objectSlug}, and changing assignees needs assign.{objectSlug}. Check the granted scopes in the /me response 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 user creating the key has.

429 Too Many Requests

"API key rate limit exceeded" — the key went over its per-minute request limit. The count resets at the start of each clock minute. Wait for the next minute, then slow down and batch requests. This limit applies to API-key traffic only.

POST /oauth/token and POST /oauth/revoke also answer 429 above 30 requests a minute from one IP address.

OAuth token exchange errors

A successful POST /oauth/token returns the raw RFC 6749 token JSON. An error uses the standard error body — status and message — with 400 or 401. The message tells you what failed:

MessageMeaning & fix
code and redirect_uri are required (400)Include both in the authorization_code body
refresh_token is required (400)Include refresh_token in the refresh_token body
invalid_grant: code is invalid, expired, or already usedCodes are single-use and live 60s — exchange immediately
invalid_grant: redirect_uri mismatchThe exchange redirect_uri must byte-match the one in the authorize request and the registered URI
invalid_grant: client_id mismatchThe client_id differs from the one the code was issued to
invalid_grant: PKCE required for public clientsPublic clients must send code_challenge (authorize) + code_verifier (token)
invalid_grant: PKCE verification failedThe code_verifier doesn't match the code_challenge (S256)
invalid_client: bad client secretConfidential client — wrong/missing client_secret
invalid_clientUnknown/inactive client_id
invalid_grant: refresh token / refresh token expiredRefresh 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)

  • 400 on the GET — unknown/inactive client_id, redirect_uri not an exact match of a registered URI, a requested scope the app isn't allowed, PKCE missing for a public client, or response_type ≠ code. Show an error screen; never redirect the browser (the backend withholds redirect_to as an open-redirect defense).
  • 400 on 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.

Inbound webhooks never arrive

A subscription delivers a signed POST to your target_url (which must be HTTPS). If nothing arrives:

  • Confirm the subscription exists and lists the object and event you expect: call GET /webhooks/subscriptions with the same credential that created it.
  • Confirm your endpoint answers with a 2xx within 5 seconds. A delivery is tried up to 3 times; a 4xx other than 408 or 429 stops the retries at once.
  • Confirm the change is one that sends an event. Some bulk changes and imports do not; Webhook events lists which.
  • Remember a change made with your own API key or OAuth token is intentionally not sent to that credential's own subscriptions.

If deliveries arrive but your check rejects them, verify the signature over <t>.<raw body> with the whole signing secret, as shown in Webhook events. See also Webhooks.

Next

  • Avoid these in the first place → Best Practices
  • Every endpoint a key can reach → Integration API Reference
Examples< PreviousBest PracticesNext >

Powered by heyo-docs

On this page

401 Unauthorized403 Forbidden429 Too Many RequestsOAuth token exchange errorsConsent screen errors (GET/POST /oauth/authorize)The call succeeds but returns fewer rows than expectedInbound webhooks never arriveNext