Troubleshooting¶
This guide covers common issues when integrating with the Contio Partner API and how to resolve them.
OAuth Authentication¶
invalid_grant - Authorization Code Failed¶
Symptom: Token exchange returns invalid_grant error with HTTP 400.
Causes:
- Authorization code expired (codes must be exchanged within 5 minutes)
- Code was already used (codes are single-use)
- Refresh token is expired or has been revoked
- Authorization code was issued to a different client
Solution:
try {
const tokens = await oauth.exchangeCodeForToken(code);
} catch (error) {
if (error.code === 'invalid_grant') {
// Restart the OAuth flow
return res.redirect('/connect');
}
}
invalid_request - Request Parameter Issues¶
Symptom: Token exchange returns invalid_request error with HTTP 400.
Causes:
redirect_uridoesn't match the one used in the authorization request- Missing required parameter
Solution:
Ensure redirect_uri matches exactly (including trailing slashes and query parameters):
// In authorization request
const authUrl = `${authorizationUrl}?redirect_uri=${encodeURIComponent('https://app.example.com/callback')}`;
// In token exchange - must match exactly
const tokens = await oauth.exchangeCodeForToken(code);
// SDK uses redirectUri from config, ensure it matches
session_expired - OTP Session Timeout¶
Symptom: Consent check or authorization fails with session_expired.
Cause: The authenticated session established during OTP verification has a 30-minute TTL. If the user takes too long between OTP verification and granting consent, the session expires.
Solution: Redirect the user back to the login page to restart the flow:
invalid_code - OTP Verification Failed¶
Symptom: /auth/verify returns invalid_code error.
Causes:
- User entered the wrong OTP code
- OTP code has expired (valid for 3 minutes)
- Code was already used
Solution: Prompt the user to request a new code:
if (error.code === 'invalid_code') {
// Show error message and option to resend code
return res.render('otp', { error: 'Invalid or expired code. Please try again.' });
}
invalid_client - Bad Credentials¶
Symptom: Any API call returns invalid_client.
Causes:
- Incorrect
client_idorclient_secret - Partner app has been deactivated
Solution:
- Verify credentials in your environment configuration
- Check that credentials match those in the Contio Partner Dashboard
- Contact support if your partner app may have been deactivated
insufficient_scope - Access Token Has No Scopes¶
Symptom: API calls return insufficient_scope or 403 error. Token introspection shows empty or missing scope field.
Causes:
- User didn't complete consent - The OAuth flow was interrupted before the user granted permission on the consent screen
- Scopes not requested - The authorization URL didn't include the
scopeparameter - Consent was revoked - User previously granted consent but later revoked it
Diagnosis: Use token introspection to check what scopes the token has:
curl -X POST https://api.contio.ai/oauth2/introspect \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=YOUR_ACCESS_TOKEN"
Response (token with scopes):
{
"active": true,
"scope": "openid profile meetings:read action-items:read",
"client_id": "your-client-id",
"exp": 1704672000
}
Response (token without scopes - consent not completed):
Solution:
The user must complete the full OAuth flow including the consent step:
- Redirect user to
/oauth2/authorizewith required scopes - User enters email and verifies via OTP
- User sees consent screen and clicks "Allow" ← This step grants scopes
- User is redirected back with authorization code
- Exchange code for tokens
// Ensure scopes are requested in authorization URL
const authUrl = oauth.getAuthorizationUrl(state, [
'openid',
'profile',
'meetings:read',
'action-items:read'
]);
// Redirect user to authUrl - they MUST complete consent
Common Mistake
If you're testing with automated flows that skip the consent UI, tokens will have no scopes. The consent screen cannot be bypassed in production.
Token Has Fewer Scopes Than Requested¶
Symptom: Token works but is missing some requested scopes.
Causes:
- User only granted partial consent (declined some permissions)
- Your partner app isn't approved for certain scopes
- Scopes were misspelled in the authorization request
Solution:
Check the token's actual scopes and handle gracefully:
const tokenInfo = await oauth.introspectToken(accessToken);
const grantedScopes = (tokenInfo.scope || '').split(' ');
if (!grantedScopes.includes('meetings:read')) {
// User didn't grant meetings access - show appropriate UI
return res.render('limited-access', {
message: 'Meeting access was not granted'
});
}
# Introspect the token to see granted scopes
curl -X POST https://api.contio.ai/oauth2/introspect \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=YOUR_ACCESS_TOKEN"
Then check the scope field in the response against what you requested.
405 Method Not Allowed¶
Symptom: API returns 405 Method Not Allowed.
Cause: Using wrong HTTP method for the endpoint.
Common mistakes:
| Wrong | Correct |
|---|---|
GET /oauth2/token | POST /oauth2/token |
GET /auth/initiate | POST /auth/initiate |
GET /auth/verify | POST /auth/verify |
Authentication Method Confusion¶
The Partner API uses three different authentication methods. Using the wrong method is a common source of errors.
Quick Reference: Which Auth Method to Use¶
| Endpoint | Auth Method | Headers |
|---|---|---|
POST /oauth2/token | HTTP Basic | Authorization: Basic base64(client_id:client_secret) |
/v1/partner/user/* | Bearer Token | Authorization: Bearer <access_token> |
/v1/partner/admin/* | API Key | X-API-Key + X-Client-ID |
Using Bearer Token on Token Endpoint¶
Symptom: POST /oauth2/token returns invalid_client or 401.
Cause: Using Authorization: Bearer instead of Authorization: Basic for token exchange.
Wrong:
curl -X POST https://api.contio.ai/oauth2/token \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d "grant_type=authorization_code&code=AUTH_CODE"
Correct:
curl -X POST https://api.contio.ai/oauth2/token \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-d "grant_type=authorization_code&code=AUTH_CODE"
Using API Key on User Endpoints¶
Symptom: /v1/partner/user/* returns 401 even with valid API key.
Cause: User endpoints require OAuth Bearer tokens, not API keys. API keys are only for admin endpoints.
Wrong:
curl https://api.contio.ai/v1/partner/user/meetings \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID"
Correct:
Using Bearer Token on Admin Endpoints¶
Symptom: /v1/partner/admin/* returns 401 with valid access token.
Cause: Admin endpoints require API key authentication, not OAuth tokens.
Wrong:
Correct:
curl https://api.contio.ai/v1/partner/admin/stats \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID"
Missing X-Client-ID Header¶
Symptom: Admin endpoint returns missing_client_id error.
Cause: Admin endpoints require both X-API-Key and X-Client-ID headers.
Solution: Always include both headers for admin endpoints:
curl https://api.contio.ai/v1/partner/admin/workflows \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID"
Basic Auth Encoding Issues¶
Symptom: Token endpoint returns invalid_client with correct credentials.
Causes:
- Credentials not Base64 encoded
- Missing colon separator between client_id and client_secret
- Extra whitespace or newlines in encoded string
Solution: Verify your Base64 encoding:
# Correct encoding (no newline with -n flag)
echo -n "client_id:client_secret" | base64
# Common mistake: missing -n adds newline
echo "client_id:client_secret" | base64 # Wrong!
API Requests¶
rate_limit_exceeded - Too Many Requests¶
Symptom: API returns HTTP 429 with rate_limit_exceeded.
Cause: You've exceeded the rate limit for your endpoint type.
| Endpoint Type | Rate Limit |
|---|---|
| User API | 100 requests/minute |
| Admin API | 50 requests/minute |
| OAuth | 20 requests/minute |
Solution: Implement exponential backoff:
async function apiCallWithRetry(fn: () => Promise<any>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.code === 'rate_limit_exceeded' && i < maxRetries - 1) {
const resetTime = error.response?.headers?.['x-ratelimit-reset'];
const waitMs = resetTime
? (parseInt(resetTime) * 1000) - Date.now()
: Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, Math.max(waitMs, 1000)));
continue;
}
throw error;
}
}
}
network_error - No Response¶
Symptom: SDK throws network_error with "No response received from server".
Causes:
- Network connectivity issues
- Request timeout
- Firewall blocking requests
Solution:
- Check network connectivity to
api.contio.ai - Increase timeout if processing large requests
- Verify firewall allows outbound HTTPS (port 443)
Webhook Issues¶
Signature Verification Failed¶
Symptom: verifyWebhookSignature() returns false.
Causes:
- Using wrong webhook secret
- Request body was modified (middleware parsing)
- Signature header missing or malformed
Solution: Use raw body for verification:
// ❌ Wrong - body may be modified
app.post('/webhooks', express.json(), (req, res) => {
const isValid = verifyWebhookSignature(req.body, signature, secret);
});
// ✅ Correct - use raw body
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = verifyWebhookSignature(req.body, signature, secret);
});
Missing Signature Header¶
Symptom: X-Contio-Signature header is empty or missing.
Cause: Request isn't coming from Contio (possibly a test request or attack).
Solution: Always verify the signature exists before processing:
const signature = req.headers['x-contio-signature'];
if (!signature) {
return res.status(400).json({ error: 'Missing signature' });
}
Using Request ID for Support¶
All error responses include a request_id field for debugging:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired",
"request_id": "req_abc123xyz"
}
When contacting support, always include the request_id to help us trace the issue.
Getting Help¶
- Email: partner-support@contio.ai
- Documentation: API Guide | OAuth Flow