Skip to content

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 Authorization header 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

{
  "error": "Human-readable error message",
  "code": "error_code",
  "request_id": "abc123xyz"
}

OAuth Error Format (RFC 6749)

OAuth endpoints return standard OAuth 2.0 error responses:

{
  "error": "invalid_grant",
  "error_description": "The authorization code has expired"
}

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

  1. Get the raw request body (before JSON parsing)
  2. Concatenate: timestamp + "." + body
  3. Compute HMAC-SHA256 using your webhook secret
  4. 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