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:
- Your server sends the
clientIdandclientSecretto the token exchange endpoint. - The server returns a JWT signed with the GoTrue JWT signing secret.
- 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 { AIChatClient } from '@aichat/sdk';
const client = new AIChatClient({
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 aichat import AIChatClient, AIChatConfig
async def main():
config = AIChatConfig(
api_url="https://api.erghi.ai",
client_id="cf_cl_9ab7c8e...",
client_secret="cf_sk_a2b9d8e7c6f5g4h3i2j1...",
workspace_id="ws-uuid"
)
async with AIChatClient(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 AIChat.SDK;
var config = new AIChatConfig("https://api.erghi.ai")
{
ClientId = "cf_cl_9ab7c8e...",
ClientSecret = "cf_sk_a2b9d8e7c6f5g4h3i2j1...",
WorkspaceId = "ws-uuid"
};
var client = new AIChatClient(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));
}