OAuth Apps

Build third-party integrations with OAuth 2.0

Build third-party applications that integrate with Coherence using OAuth 2.0. OAuth apps allow your users to securely authorize access to their Coherence data without sharing credentials.

Overview

OAuth 2.0 is the industry-standard protocol for authorization. When you build an OAuth app, users can grant your application access to their Coherence workspace data. Your app receives tokens to make API requests on their behalf.

Use Cases

  • SaaS integrations - Connect Coherence to your product
  • Marketplace apps - Build apps for all Coherence users
  • Automation tools - Create workflows that act on user data
  • Analytics platforms - Access data for reporting and insights
  • Mobile apps - Build native experiences with Coherence data

Registering an App

Create Your App

  1. Go to Settings > Developers > OAuth Apps
  2. Click Create App
  3. Fill in your app details:
FieldDescription
App NameDisplay name shown to users during authorization
DescriptionBrief description of what your app does
Website URLYour app's homepage
Privacy Policy URLLink to your privacy policy (required for public apps)
Terms of Service URLLink to your terms (required for public apps)

Configure Redirect URIs

Add one or more redirect URIs where users will be sent after authorization:

https://yourapp.com/oauth/callback
https://localhost:3000/oauth/callback (for development)

Redirect URIs must use HTTPS in production. HTTP is only allowed for localhost during development.

Select Scopes

Choose the permissions your app needs. Request only the scopes necessary for your app's functionality.

Get Your Credentials

After creating your app, you'll receive:

  • Client ID - Public identifier for your app (e.g., cid_abc123xyz)
  • Client Secret - Private key for token exchange (e.g., cs_secret_xyz789)

Your client secret is shown only once. Store it securely - you cannot retrieve it later. If compromised, rotate it immediately in your app settings.

OAuth 2.0 Flow

Step 1: Authorization Request

Redirect users to the authorization URL:

https://app.getcoherence.io/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/oauth/callback
  &response_type=code
  &scope=read:records write:records
  &state=random_state_string
ParameterRequiredDescription
client_idYesYour app's client ID
redirect_uriYesMust match a registered redirect URI
response_typeYesAlways code for authorization code flow
scopeYesSpace-separated list of requested scopes
stateRecommendedRandom string to prevent CSRF attacks

Users see a consent screen displaying:

  • Your app name and logo
  • Requested permissions
  • Approve/Deny buttons

If the user approves, they're redirected to your callback URL with an authorization code:

https://yourapp.com/oauth/callback?code=auth_code_abc123&state=random_state_string

If denied:

https://yourapp.com/oauth/callback?error=access_denied&error_description=User+denied+access

Step 3: Exchange Code for Tokens

Exchange the authorization code for access and refresh tokens:

curl -X POST "https://api.getcoherence.io/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=auth_code_abc123" \
  -d "redirect_uri=https://yourapp.com/oauth/callback"

Response:

{
  "access_token": "at_eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "rt_dGhpcyBpcyBhIHJlZnJl...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:records write:records",
  "workspace_id": "ws_abc123"
}

Authorization codes expire after 10 minutes and can only be used once.

Available Scopes

Request only the scopes your app needs. Users can see what permissions you're requesting.

Record Scopes

ScopeDescription
read:recordsRead records from all modules
write:recordsCreate and update records
delete:recordsDelete records

Module Scopes

ScopeDescription
read:modulesView module schemas and fields
write:modulesCreate and modify modules (admin only)

User Scopes

ScopeDescription
read:usersView workspace users and teams
read:profileAccess the authorizing user's profile

Activity Scopes

ScopeDescription
read:activityView activity feed and audit logs

Communication Scopes

ScopeDescription
read:emailRead email conversations
send:emailSend emails on behalf of the user

Webhook Scopes

ScopeDescription
read:webhooksView webhook configurations
write:webhooksCreate and manage webhooks

Token Management

Token Expiration

Token TypeExpiration
Access Token1 hour (3600 seconds)
Refresh Token30 days

Refreshing Access Tokens

When an access token expires, use the refresh token to get a new one:

curl -X POST "https://api.getcoherence.io/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=rt_dGhpcyBpcyBhIHJlZnJl..."

Response:

{
  "access_token": "at_bmV3IGFjY2VzcyB0b2tl...",
  "refresh_token": "rt_bmV3IHJlZnJlc2ggdG9r...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:records write:records"
}

Store the new refresh token - it replaces the previous one. Refresh tokens are rotated for security.

Revoking Tokens

Users can revoke access from their Coherence settings. You can also programmatically revoke tokens:

curl -X POST "https://api.getcoherence.io/oauth/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "token=at_eyJhbGciOiJIUzI1NiIs..."

This revokes both the access token and its associated refresh token.

Making Authenticated Requests

Include the access token in the Authorization header:

curl -X GET "https://api.getcoherence.io/v1/modules/contacts/records" \
  -H "Authorization: Bearer at_eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json"

Example: Create a Record

curl -X POST "https://api.getcoherence.io/v1/modules/contacts/records" \
  -H "Authorization: Bearer at_eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Smith",
    "email": "[email protected]",
    "company": "Acme Inc"
  }'

Example: Get Current User

curl -X GET "https://api.getcoherence.io/v1/users/me" \
  -H "Authorization: Bearer at_eyJhbGciOiJIUzI1NiIs..."

Response:

{
  "data": {
    "id": "usr_abc123",
    "email": "[email protected]",
    "name": "Jane Smith",
    "workspace_id": "ws_xyz789"
  }
}

Handling Token Refresh in Your App

Implement automatic token refresh to ensure uninterrupted access:

interface TokenData {
  accessToken: string;
  refreshToken: string;
  expiresAt: number;
}
 
class CoherenceClient {
  private tokenData: TokenData;
  private clientId: string;
  private clientSecret: string;
 
  async request(endpoint: string, options: RequestInit = {}) {
    // Check if token is expired or will expire soon (5 min buffer)
    if (Date.now() >= this.tokenData.expiresAt - 300000) {
      await this.refreshAccessToken();
    }
 
    const response = await fetch(`https://api.getcoherence.io/v1${endpoint}`, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${this.tokenData.accessToken}`,
        'Content-Type': 'application/json',
      },
    });
 
    // Handle token expiration during request
    if (response.status === 401) {
      await this.refreshAccessToken();
      return this.request(endpoint, options);
    }
 
    return response.json();
  }
 
  private async refreshAccessToken() {
    const response = await fetch('https://api.getcoherence.io/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        client_id: this.clientId,
        client_secret: this.clientSecret,
        refresh_token: this.tokenData.refreshToken,
      }),
    });
 
    const data = await response.json();
 
    this.tokenData = {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt: Date.now() + (data.expires_in * 1000),
    };
 
    // Persist updated tokens to your storage
    await this.saveTokens(this.tokenData);
  }
}

App Review Process

Development Mode

New apps start in development mode:

  • Only workspace admins can authorize
  • Limited to 10 authorized users
  • Not listed in the app marketplace

Submitting for Review

To make your app public:

  1. Go to Settings > Developers > OAuth Apps
  2. Select your app
  3. Click Submit for Review
  4. Provide:
    • App description and screenshots
    • Video demo (optional but recommended)
    • Support contact information
    • Data handling practices

Review Criteria

Apps are reviewed for:

  • Security - Proper handling of tokens and user data
  • Functionality - App works as described
  • User Experience - Clear authorization flow
  • Compliance - Privacy policy and terms are complete

Review typically takes 3-5 business days.

After Approval

Approved apps can:

  • Be installed by any Coherence user
  • Be listed in the app marketplace (optional)
  • Have unlimited authorized users

Testing in Sandbox

Sandbox Environment

Use our sandbox for development and testing:

Authorization: https://sandbox.app.getcoherence.io/oauth/authorize
Token endpoint: https://sandbox.api.getcoherence.io/oauth/token
API base URL: https://sandbox.api.getcoherence.io/v1

Creating Sandbox Apps

  1. Create a separate OAuth app for sandbox testing
  2. Use sandbox redirect URIs (can include localhost)
  3. Test the full authorization flow

Sandbox data is reset weekly. Do not use sandbox for production workloads.

Test Users

Create test users in your sandbox workspace to simulate different permission scenarios.

Security Best Practices

Store Secrets Securely

Do:

  • Store client secrets in environment variables or secret managers
  • Use encrypted storage for access and refresh tokens
  • Implement proper access controls for token storage

Don't:

  • Commit secrets to version control
  • Log tokens or secrets
  • Store secrets in client-side code

Use PKCE for Mobile and SPA

For public clients (mobile apps, single-page apps), use PKCE (Proof Key for Code Exchange):

import crypto from 'crypto';
 
// Generate code verifier (43-128 characters)
const codeVerifier = crypto.randomBytes(32).toString('base64url');
 
// Generate code challenge
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

Include in authorization request:

https://app.getcoherence.io/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=read:records
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Include verifier in token exchange:

curl -X POST "https://api.getcoherence.io/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code=auth_code_abc123" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

Validate Redirect URIs

  • Register all valid redirect URIs in your app settings
  • Use exact matching - avoid wildcards
  • Validate the state parameter on callback

Handle Errors Gracefully

async function handleCallback(code: string, state: string) {
  // Verify state matches what you sent
  if (state !== storedState) {
    throw new Error('Invalid state parameter - possible CSRF attack');
  }
 
  try {
    const tokens = await exchangeCodeForTokens(code);
    return tokens;
  } catch (error) {
    if (error.code === 'invalid_grant') {
      // Code expired or already used
      return redirectToAuthorization();
    }
    throw error;
  }
}

Token Security Checklist

  • Store tokens encrypted at rest
  • Use secure, httpOnly cookies for web apps
  • Implement token rotation on refresh
  • Set appropriate token expiration
  • Revoke tokens when users disconnect
  • Monitor for suspicious token usage

Related: Authentication | API Overview