REST API (Direct)¶
This guide shows how to integrate with the Contio Partner API using direct HTTP requests. For SDK-based integration, see the Quick Start guide.
Base URL¶
| Environment | Base URL |
|---|---|
| Production | https://api.contio.ai |
| Beta | https://beta.api.contio.ai |
Prerequisites¶
Contact partner-support@contio.ai to obtain:
- Client ID and Client Secret (for OAuth 2.0 user operations)
- API Key (for admin operations)
Choose Your Integration Path¶
The Partner API uses two distinct authentication patterns, each designed for a specific context:
| Context | Auth Method | Purpose |
|---|---|---|
| User Data Access | OAuth 2.0 (Authorization header) | Access user meetings and action items on behalf of users who have granted consent |
| Admin Operations | API Key (X-API-Key + X-Client-ID headers) | Manage your integration: automations, connections, webhooks |
Why Different Auth Patterns?
The distinct authentication methods reinforce an important conceptual separation:
-
OAuth endpoints use the standard
Authorizationheader because you're acting on behalf of a user who explicitly granted access. The access token represents delegated user consent. -
Admin endpoints use custom headers because you're performing server-to-server operations for your own integration—no user involvement. The API key represents your partner application's identity.
This separation helps ensure you always know which context you're operating in and prevents accidentally mixing user-delegated and admin operations.
Path A: User Data Access (OAuth 2.0)¶
Use OAuth 2.0 to access user meetings and action items on behalf of connected users.
Step 1: Redirect User to Authorization¶
Construct the authorization URL and redirect the user:
https://api.contio.ai/oauth2/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/callback&
scope=openid profile meetings:read action-items:read&
state=RANDOM_STATE_FOR_CSRF
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | Your partner app's client ID |
redirect_uri | Yes | URL-encoded callback URL (must match registered URI) |
scope | Yes | Space-separated list of scopes |
state | Yes | Random string for CSRF protection |
code_challenge | No | PKCE challenge (recommended for public clients) |
code_challenge_method | No | Must be S256 if using PKCE |
Step 2: Exchange Authorization Code for Tokens¶
After the user authorizes, they're redirected to your callback with a code parameter.
Client credentials must be provided via HTTP Basic authentication (Base64-encoded client_id:client_secret):
# Encode credentials: echo -n "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" | base64
curl -X POST https://api.contio.ai/oauth2/token \
-H "Authorization: Basic $(echo -n 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://your-app.com/callback"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIi...",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "openid profile meetings:read action-items:read"
}
Step 3: Make API Calls with Access Token¶
Include the access token in the Authorization header:
# Get user's meetings
curl -X GET "https://api.contio.ai/v1/partner/user/meetings?limit=10" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/json"
Response:
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Weekly Team Sync",
"scheduled_start": "2024-01-15T10:00:00Z",
"scheduled_end": "2024-01-15T11:00:00Z",
"status": "completed"
}
],
"total": 1,
"limit": 10,
"offset": 0
}
Step 4: Refresh Tokens¶
Access tokens expire after 24 hours. Use the refresh token to get new tokens:
curl -X POST https://api.contio.ai/oauth2/token \
-H "Authorization: Basic $(echo -n 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' | base64)" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN"
Token Lifetimes
| Token Type | Validity |
|---|---|
| Authorization code | 5 minutes |
| Access token | 24 hours |
| Refresh token | 30 days |
Path B: Admin Operations (API Key)¶
Use API key authentication for server-to-server admin operations.
Required Headers¶
| Header | Description |
|---|---|
X-API-Key | Your partner app's API key |
X-Client-ID | Your partner app's client ID |
Example: Get Partner App Info¶
curl -X GET https://api.contio.ai/v1/partner/admin/app \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID" \
-H "Accept: application/json"
Response:
{
"id": "app_abc123",
"name": "My Partner App",
"status": "active",
"webhook_enabled": true,
"webhook_url": "https://example.com/webhooks"
}
Example: List Automations¶
curl -X GET https://api.contio.ai/v1/partner/admin/automations \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID" \
-H "Accept: application/json"
Example: Create an Automation¶
curl -X POST https://api.contio.ai/v1/partner/admin/automations \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Client-ID: YOUR_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM Sync",
"description": "Sync action items to our CRM",
"trigger_type": "action_item_created",
"actions": [{
"type": "webhook",
"webhook_url": "https://your-app.com/webhooks/contio"
}]
}'
Error Handling¶
The API returns consistent error responses across all endpoints. Every error response includes a request_id for support troubleshooting.
Partner API Error Format¶
OAuth Error Format (RFC 6749)¶
OAuth endpoints return standard OAuth 2.0 error responses:
Common Error Codes¶
| HTTP Status | Code | Description |
|---|---|---|
| 400 | invalid_request | Malformed request or missing required parameters |
| 400 | invalid_grant | Authorization code expired or already used |
| 401 | unauthorized | Missing or invalid authentication |
| 401 | missing_api_key | API key header not provided |
| 401 | missing_client_id | Client ID header not provided |
| 401 | invalid_credentials | API key or client ID is invalid |
| 401 | missing_authorization | Authorization header not provided |
| 401 | invalid_authorization_format | Authorization header must be Bearer <token> |
| 403 | forbidden | Insufficient permissions for this operation |
| 403 | partner_app_inactive | Partner app is suspended or inactive |
| 403 | insufficient_scope | Access token lacks required scopes |
| 404 | not_found | Requested resource does not exist |
| 429 | rate_limit_exceeded | Too many requests (see Rate Limits below) |
| 500 | internal_server_error | Unexpected server error |
Handling Authentication Errors¶
# Example: Missing API Key
curl -X GET https://api.contio.ai/v1/partner/admin/app \
-H "X-Client-ID: YOUR_CLIENT_ID"
# Response (401):
{
"error": "Missing API key",
"code": "missing_api_key",
"request_id": "req_abc123"
}
# Example: Expired Access Token
curl -X GET https://api.contio.ai/v1/partner/user/meetings \
-H "Authorization: Bearer EXPIRED_TOKEN"
# Response (401):
{
"error": "Invalid or expired access token",
"code": "unauthorized",
"request_id": "req_xyz789"
}
Rate Limits¶
Rate limits are enforced per IP address over a rolling 5-minute window.
| Endpoint Type | Rate Limit | Approx. per Minute |
|---|---|---|
User API (/v1/partner/user/*) | 1,000 requests / 5 min | ~200/min |
Admin API (/v1/partner/admin/*) | 500 requests / 5 min | ~100/min |
OAuth (/oauth2/*) | 100 requests / 5 min | ~20/min |
When rate limited, the API returns:
{
"error": "rate_limit_exceeded",
"error_description": "Too many requests. Please retry after the rate limit window resets."
}
Webhook Verification¶
When receiving webhooks, always verify the signature to ensure the request came from Contio.
Webhook Headers¶
| Header | Description |
|---|---|
X-Contio-Signature | HMAC-SHA256 signature of the request body |
X-Contio-Timestamp | Unix timestamp when the webhook was sent |
Content-Type | Always application/json |
Signature Verification Algorithm¶
- Get the raw request body (before JSON parsing)
- Concatenate:
timestamp + "." + body - Compute HMAC-SHA256 using your webhook secret
- Compare with the provided signature
Python Example:
import hmac
import hashlib
import time
def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Check timestamp is within 5 minutes to prevent replay attacks
if abs(time.time() - int(timestamp)) > 300:
return False
# Compute expected signature
message = f"{timestamp}.{body.decode('utf-8')}"
expected = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
Node.js Example:
const crypto = require('crypto');
function verifyWebhook(body, signature, timestamp, secret) {
// Check timestamp is within 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
// Compute expected signature
const message = `${timestamp}.${body}`;
const expected = crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
Next Steps¶
- Authentication Guide - Deep dive into OAuth 2.0 and API key configuration
- Webhook Events - Real-time event notifications
- API Reference - Complete endpoint documentation