Skip to main content

Machine-to-Machine (M2M) API Authentication & SDK Integration Guide

This guide details how to securely connect backend applications to the Erghi API using the Client Credentials Flow (Machine-to-Machine authentication).


Overview

For backend services, cron jobs, or server-side integrations, using standard user login credentials is not secure or practical. Erghi supports the OAuth2 Client Credentials grant type, allowing you to authenticate using a clientId and clientSecret.

When you authenticate with client credentials:

  1. Your server sends the clientId and clientSecret to the token exchange endpoint.
  2. The server returns a JWT signed with the GoTrue JWT signing secret.
  3. The SDKs automatically handle caching and refreshing this token so your requests remain authenticated.

Key Management API

Before using M2M authentication, you must generate credentials for your workspace.

[!NOTE] Key management endpoints require standard user authentication with an Admin role.

1. Generate Client Credentials

Create a new Client ID and Client Secret pair for a workspace.

POST /api/v1/auth/keys
Authorization: Bearer <user-jwt-token>
Content-Type: application/json

{
"workspaceId": "ws-uuid",
"label": "Billing Service Sync Integration"
}

Response 201 Created

{
"id": "cred-uuid",
"workspaceId": "ws-uuid",
"label": "Billing Service Sync Integration",
"clientId": "cf_cl_9ab7c8e...",
"clientSecret": "cf_sk_a2b9d8e7c6f5g4h3i2j1...",
"createdAt": "2026-06-23T08:00:00Z"
}

[!WARNING] The clientSecret is only returned once upon generation. Save it securely. It is hashed on the server using PBKDF2.

2. List Client Credentials

Retrieve all active credential pairs for your workspace.

GET /api/v1/auth/keys?workspaceId=ws-uuid
Authorization: Bearer <user-jwt-token>

Response 200 OK

[
{
"id": "cred-uuid",
"workspaceId": "ws-uuid",
"label": "Billing Service Sync Integration",
"clientId": "cf_cl_9ab7c8e...",
"createdAt": "2026-06-23T08:00:00Z"
}
]

3. Revoke Client Credentials

Immediately deactivate a credential pair. Any API calls using the revoked credentials will return 401 Unauthorized.

DELETE /api/v1/auth/keys/cred-uuid
Authorization: Bearer <user-jwt-token>

Response 204 No Content


Token Exchange Endpoint

This public endpoint is used by SDKs and external applications to exchange client credentials for a bearer token.

POST /api/v1/auth/token
Content-Type: application/json

{
"grant_type": "client_credentials",
"client_id": "cf_cl_9ab7c8e...",
"client_secret": "cf_sk_a2b9d8e7c6f5g4h3i2j1..."
}

Response 200 OK

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}

SDK Integration Examples

Official SDKs natively support client credentials. They will automatically intercept requests, retrieve a JWT, cache it, and transparently handle token expiration.

1. JavaScript/TypeScript (Node.js)

import { ErghiClient } from '@erghi-sdk/javascript';

const client = new ErghiClient({
apiUrl: 'https://api.erghi.ai',
clientId: 'cf_cl_9ab7c8e...',
clientSecret: 'cf_sk_a2b9d8e7c6f5g4h3i2j1...',
workspaceId: 'ws-uuid'
});

// The client automatically fetches the token on the first request
const conversations = await client.chat.getConversations();
console.log(conversations);

2. Python

import asyncio
from erghi import ErghiClient, ErghiConfig

async def main():
config = ErghiConfig(
api_url="https://api.erghi.ai",
client_id="cf_cl_9ab7c8e...",
client_secret="cf_sk_a2b9d8e7c6f5g4h3i2j1...",
workspace_id="ws-uuid"
)

async with ErghiClient(config) as client:
conversations = await client.chat.get_conversations()
for conv in conversations:
print(f"Conversation: {conv.id} - Status: {conv.status}")

asyncio.run(main())

3. .NET (C#)

using Erghi.SDK;

var config = new ErghiConfig
{
ApiUrl = "https://api.erghi.ai",
ClientId = "cf_cl_9ab7c8e...",
ClientSecret = "cf_sk_a2b9d8e7c6f5g4h3i2j1...",
WorkspaceId = "ws-uuid"
};

await using var client = new ErghiClient(config);

// The client manages token acquisition and refreshing asynchronously
var conversations = await client.Chat.GetConversationsAsync();
foreach (var conv in conversations.Data)
{
Console.WriteLine($"ID: {conv.Id}, Status: {conv.Status}");
}

4. Flutter / Dart

import 'package:aichat_sdk/aichat_sdk.dart';

void main() async {
final config = AIChatConfig(
apiUrl: 'https://api.erghi.ai',
clientId: 'cf_cl_9ab7c8e...',
clientSecret: 'cf_sk_a2b9d8e7c6f5g4h3i2j1...',
workspaceId: 'ws-uuid',
);

final client = AIChatClient(config);

try {
final response = await client.chat.getConversations();
for (var conversation in response.data) {
print('Conversation ID: ${conversation.id}');
}
} catch (e) {
print('Failed to get conversations: $e');
}
}

5. Swift

import Foundation
import AIChatSDK

let config = AIChatConfig(
apiURL: URL(string: "https://api.erghi.ai")!,
clientId: "cf_cl_9ab7c8e...",
clientSecret: "cf_sk_a2b9d8e7c6f5g4h3i2j1..."
)

let client = AIChatClient(config: config)

Task {
do {
let conversations = try await client.chat.getConversations()
for conversation in conversations.data {
print("Conversation ID: \(conversation.id)")
}
} catch {
print("Error: \(error)")
}
}

Webhooks

Webhooks notify your backend of real-time events, such as when a new message is received or a conversation is closed.

Event Payload Structure

All webhook events use a standard envelope structure:

{
"event": "message.created",
"timestamp": "2026-06-23T08:05:00Z",
"workspaceId": "ws-uuid",
"data": {
"id": "msg-uuid",
"conversationId": "conv-uuid",
"senderId": "visitor-uuid",
"senderType": "visitor",
"content": "Hi! Can you help me?",
"createdAt": "2026-06-23T08:05:00Z"
}
}

Verifying Signatures

Erghi includes a signature in the request headers (X-Erghi-Signature) using HMAC-SHA256 to ensure payloads originate from our platform.

To verify a webhook signature in Node.js:

import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
const hash = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(signature));
}