OAuth API

Connect to external services using OAuth 2.0

OAuth API

Connect your integrations to external services using OAuth 2.0. Users authorize once, and Ferni securely stores and refreshes tokens automatically.

Base URL:https://api.ferni.ai/api/v2/developers/oauth


Register OAuth Provider

Register an OAuth provider configuration.

POST /oauth/providers

Request Body

{
  "name": "google-calendar",
  "displayName": "Google Calendar",
  "description": "Access user's Google Calendar",
  "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
  "tokenUrl": "https://oauth2.googleapis.com/token",
  "clientId": "your-client-id.apps.googleusercontent.com",
  "clientSecret": "{{secrets.GOOGLE_CLIENT_SECRET}}",
  "scopes": [
    "https://www.googleapis.com/auth/calendar.readonly",
    "https://www.googleapis.com/auth/calendar.events"
  ],
  "secrets": {
    "GOOGLE_CLIENT_SECRET": "your-client-secret"
  }
}

Parameters

FieldTypeRequiredDescription
namestringYesUnique identifier (kebab-case)
displayNamestringYesName shown to users
descriptionstringYesWhat this connection provides
authorizationUrlstringYesOAuth authorization endpoint
tokenUrlstringYesOAuth token endpoint
clientIdstringYesYour OAuth client ID
clientSecretstringYesYour client secret (use {{secrets.X}})
scopesstring[]YesRequired OAuth scopes
secretsobjectNoSecret values (encrypted at rest)
additionalParamsobjectNoExtra params for authorization URL

Response

{
  "success": true,
  "data": {
    "id": "oauth_abc123xyz",
    "name": "google-calendar",
    "displayName": "Google Calendar",
    "description": "Access user's Google Calendar",
    "scopes": ["calendar.readonly", "calendar.events"],
    "status": "active",
    "connectionCount": 0,
    "createdAt": "2026-01-11T10:00:00Z"
  }
}

List Providers

Get all registered OAuth providers.

GET /oauth/providers

Response

{
  "success": true,
  "data": [
    {
      "id": "oauth_abc123xyz",
      "name": "google-calendar",
      "displayName": "Google Calendar",
      "scopes": ["calendar.readonly", "calendar.events"],
      "status": "active",
      "connectionCount": 45,
      "createdAt": "2026-01-11T10:00:00Z"
    }
  ]
}

Get Provider

Get a specific provider.

GET /oauth/providers/:id

Update Provider

Update provider configuration.

PUT /oauth/providers/:id

Note: Changing scopes requires users to re-authorize.


Delete Provider

Remove a provider and all associated tokens.

DELETE /oauth/providers/:id

Warning: This revokes all user connections.


Start Authorization

Generate an authorization URL for a user.

POST /oauth/authorize

Request Body

{
  "providerId": "oauth_abc123xyz",
  "userId": "usr_456",
  "redirectUri": "https://yourapp.com/oauth/callback",
  "state": "optional-custom-state"
}

Response

{
  "success": true,
  "data": {
    "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth?client_id=xxx&redirect_uri=xxx&scope=xxx&state=xxx&response_type=code",
    "state": "ferni_oauth_abc123_usr456_1704985200"
  }
}

Redirect the user to authorizationUrl. After authorization, they'll be redirected to your redirectUri with a code parameter.


Handle Callback

Exchange the authorization code for tokens.

POST /oauth/callback

Request Body

{
  "providerId": "oauth_abc123xyz",
  "code": "authorization-code-from-callback",
  "state": "ferni_oauth_abc123_usr456_1704985200",
  "redirectUri": "https://yourapp.com/oauth/callback"
}

Response

{
  "success": true,
  "data": {
    "tokenId": "tok_xyz789",
    "providerId": "oauth_abc123xyz",
    "userId": "usr_456",
    "scopes": ["calendar.readonly", "calendar.events"],
    "expiresAt": "2026-01-11T11:00:00Z",
    "createdAt": "2026-01-11T10:00:00Z"
  }
}

Tokens are stored encrypted. Ferni automatically refreshes them before expiry.


List User Tokens

Get all OAuth connections for a user.

GET /oauth/tokens

Query Parameters

FieldTypeDescription
userIdstringFilter by user ID
providerIdstringFilter by provider

Response

{
  "success": true,
  "data": [
    {
      "id": "tok_xyz789",
      "providerId": "oauth_abc123xyz",
      "providerName": "google-calendar",
      "userId": "usr_456",
      "scopes": ["calendar.readonly", "calendar.events"],
      "status": "active",
      "expiresAt": "2026-01-11T11:00:00Z",
      "lastUsedAt": "2026-01-11T10:30:00Z",
      "createdAt": "2026-01-11T10:00:00Z"
    }
  ]
}

Get Token

Get a specific token (for debugging).

GET /oauth/tokens/:id

Note: Actual token values are never returned. Only metadata.


Revoke Token

Disconnect a user's OAuth connection.

DELETE /oauth/tokens/:id

Response

{
  "success": true,
  "data": {
    "revoked": true,
    "tokenId": "tok_xyz789"
  }
}

Refresh Token

Manually refresh a token (usually automatic).

POST /oauth/tokens/:id/refresh

Response

{
  "success": true,
  "data": {
    "tokenId": "tok_xyz789",
    "refreshed": true,
    "expiresAt": "2026-01-11T12:00:00Z"
  }
}

Check Connection Status

Verify a user has a valid connection.

GET /oauth/tokens/:userId/:providerId/status

Response

{
  "success": true,
  "data": {
    "connected": true,
    "providerId": "oauth_abc123xyz",
    "providerName": "google-calendar",
    "scopes": ["calendar.readonly", "calendar.events"],
    "expiresAt": "2026-01-11T11:00:00Z",
    "needsReauthorization": false
  }
}

Using Tokens in Tools

When you have a user's OAuth token, use it in MCP servers or tools:

In MCP Server Headers

{
  "headers": {
    "Authorization": "Bearer {{oauth.google-calendar.access_token}}"
  }
}

In Webhook Tools

{
  "config": {
    "url": "https://api.example.com/data",
    "headers": {
      "Authorization": "Bearer {{oauth.your-provider.access_token}}"
    }
  }
}

Ferni automatically:

  1. Retrieves the user's token
  2. Refreshes if expired
  3. Injects into the request

Common OAuth Providers

Google

{
  "name": "google",
  "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
  "tokenUrl": "https://oauth2.googleapis.com/token",
  "additionalParams": {
    "access_type": "offline",
    "prompt": "consent"
  }
}

Microsoft

{
  "name": "microsoft",
  "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
  "tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token"
}

Salesforce

{
  "name": "salesforce",
  "authorizationUrl": "https://login.salesforce.com/services/oauth2/authorize",
  "tokenUrl": "https://login.salesforce.com/services/oauth2/token"
}

Slack

{
  "name": "slack",
  "authorizationUrl": "https://slack.com/oauth/v2/authorize",
  "tokenUrl": "https://slack.com/api/oauth.v2.access"
}

Token Status Values

StatusDescription
activeToken is valid and usable
expiredToken expired, needs refresh
revokedUser or provider revoked access
errorRefresh failed, needs re-authorization

Security

  • Client secrets are encrypted at rest (AES-256-GCM)
  • Access tokens are encrypted in transit and at rest
  • Refresh tokens are never exposed via API
  • Automatic token rotation on refresh
  • All OAuth state includes CSRF protection

Error Codes

CodeDescription
OAUTH_INVALID_STATEState parameter mismatch (CSRF protection)
OAUTH_CODE_EXPIREDAuthorization code expired
OAUTH_REFRESH_FAILEDCould not refresh token
OAUTH_REVOKEDUser revoked access
OAUTH_INVALID_SCOPERequested scope not allowed

Best Practices

  1. Request minimal scopes — Only ask for what you need
  2. Handle revocation — Users can revoke anytime
  3. Check connection before use — Verify token status
  4. Use refresh tokens — Always request offline_access for Google
  5. Provide clear consent — Explain why you need access

Related