Task Interface
One endpoint. Plain English. The agent handles everything else.
The shift
Traditional integrations force you to learn endpoints, map fields, version schemas, and handle every edge case. The Chat Agent Gateway replaces all of that with agentic communication. Describe what you need. SOIS's own agent interprets your intent, negotiates the details, executes across your workspace, and replies with the result. This is the on-ramp for callers that bring no AI of their own. If you already have an agent, connect it directly over the MCP Server instead.
No connectors. No middleware. No third-party automation layer.
Authentication
Every message requires two credentials:
| Header | Value | What it does |
|---|---|---|
Authorization |
Bearer <token> |
Proves who you are |
X-Api-Key |
sois_... |
Controls your budget, webhook, and signing |
Why two? Your token proves identity. Your key controls spend. If either leaks alone, it's useless without the other. One user can have multiple keys with different budgets for different integrations.
Get both from Settings in your Sois AI workspace.
Send a message
POST /api/agent
Headers
Authorization: Bearer <your_token>
X-Api-Key: sois_...
Content-Type: application/json
Body
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | Yes | Tell the agent what you need, in plain English, up to 10,000 characters |
context.entity_type |
string | No | Hint: contact, task, document, inbox, etc. |
context.entity_id |
string | No | A specific record to work with |
context.action |
string | No | Hint: create, update, search, summarize |
metadata |
object | No | Any extra key-value data for the agent |
Example
curl -X POST "https://your-workspace.sois.ai/api/agent" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Api-Key: sois_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Create a contact for John Smith at Acme Corp, [email protected], +44 7700 900000",
"context": { "entity_type": "contact", "action": "create" }
}'
Response 202 Accepted
{
"conversation_uuid": "6cafd8ba-dd51-4411-808e-671d1dd59561",
"status": "accepted",
"message": "Your request has been queued."
}
Your message is in the gateway. The agent takes it from here.
Receive the reply
You have two options: wait for the webhook, or poll the conversation.
Option 1: Webhook (recommended)
If your key has a response_url, the agent delivers the result directly to you when it's done: signed, verified, no polling required.
POST https://your-app.com/webhook
Content-Type: application/json
X-Signature: <hmac_sha256>
X-Conversation-UUID: 6cafd8ba-...
Resolved:
{
"conversation_uuid": "6cafd8ba-...",
"status": "resolved",
"response": "Contact created: John Smith, Acme Corp, [email protected], +44 7700 900000."
}
Failed:
{
"conversation_uuid": "6cafd8ba-...",
"status": "failed",
"error": "An error occurred while processing your request."
}
Option 2: Poll the conversation
GET /api/agent/{conversation_uuid}
Authorization: Bearer <your_token>
X-Api-Key: sois_...
Response 200 OK:
{
"conversation_uuid": "6cafd8ba-...",
"status": "resolved",
"turns": [
{
"role": "user",
"payload": { "message": "Find all contacts from London" },
"timestamp": "2026-02-12T01:07:03+00:00"
},
{
"role": "agent",
"payload": {
"content": "I found 12 contacts based in London..."
},
"timestamp": "2026-02-12T01:07:13+00:00"
}
],
"webhook": {
"status": "delivered",
"delivered_at": "2026-02-12T01:07:14+00:00"
},
"resolved_at": "2026-02-12T01:07:13+00:00",
"expires_at": "2026-02-13T01:07:03+00:00",
"created_at": "2026-02-12T01:07:03+00:00"
}
Conversation statuses
| Status | What's happening |
|---|---|
open |
In the gateway, waiting for the agent |
processing |
The agent is working on it |
resolved |
Done. The reply is in the final turn |
failed |
Couldn't complete after retries |
Verifying webhook signatures
Every webhook is signed with HMAC-SHA256 using your key's signing secret. Always verify before processing.
Python:
import hmac, hashlib
expected = hmac.new(
key=your_signing_secret.encode(),
msg=raw_request_body.encode(),
digestmod=hashlib.sha256
).hexdigest()
assert expected == request.headers['X-Signature']
Node.js:
const crypto = require('crypto');
const expected = crypto
.createHmac('sha256', signingSecret)
.update(rawBody)
.digest('hex');
if (expected !== req.headers['x-signature']) {
throw new Error('Invalid signature');
}
PHP:
$expected = hash_hmac('sha256', $rawBody, $signingSecret);
if (!hash_equals($expected, $request->header('X-Signature'))) {
abort(403, 'Invalid signature');
}
Example conversations
The agent has full access to your Sois AI workspace. Here are some things you can ask:
Search:
{ "message": "Find all contacts from London" }
Create:
{
"message": "Create a contact: Jane Doe, CEO of Acme Corp, [email protected]",
"context": { "entity_type": "contact", "action": "create" }
}
Email:
{
"message": "Send an email to [email protected] introducing our new product line",
"context": { "entity_type": "inbox", "action": "compose" }
}
Tasks:
{
"message": "Create a task: Research competitor pricing for Q1 2026",
"context": { "entity_type": "task", "action": "create" }
}
Summarise:
{
"message": "Give me a full summary of this contact",
"context": { "entity_type": "contact", "entity_id": "abc-123", "action": "summarize" }
}
Multi-step:
{ "message": "Find the Q1 report, summarise it, and email the summary to the sales team" }
The agent negotiates ambiguity. If it needs to search before it can act, it searches. If multiple records match, it picks the best one and tells you why. No rigid schemas, just intent.
Key management
Manage your keys from Settings > API Keys in your Sois AI workspace, or programmatically.
Create a key
POST /api/api-keys
{
"label": "My CRM Integration",
"response_url": "https://your-app.com/webhook",
"budget_usd_cents": 5000,
"budget_period": "monthly"
}
The response includes your key (shown once, so copy it immediately) and your signing secret for webhook verification.
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | No | A name for this key |
response_url |
url | No | Where to deliver webhook replies |
budget_usd_cents |
integer | No | Spending cap in USD cents |
budget_period |
string | No | daily, monthly, or total |
Manage keys
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/api-keys |
List all your keys |
POST |
/api/api-keys |
Create a new key |
GET |
/api/api-keys/{id} |
Key details and usage stats |
PUT |
/api/api-keys/{id} |
Update label, webhook URL, or budget |
DELETE |
/api/api-keys/{id} |
Revoke a key |
Budget controls
Traditional integrations throttle you with arbitrary rate limits: 100 requests per minute, 10,000 per day. The Chat Agent Gateway uses dollars.
Set a budget per key. The agent charges credits per action. You control what each integration is worth.
| Field | Description |
|---|---|
budget_usd_cents |
Cap in USD cents (e.g. 5000 = $50). null = unlimited. |
budget_period |
daily resets at midnight, monthly resets on the 1st, total never resets |
When a budget is exceeded, messages return 429 with a clear explanation, not a cryptic error code.
Errors
Every error follows the same format:
{
"error": {
"code": "ERROR_CODE",
"message": "A clear, human-readable explanation."
}
}
| HTTP | Code | What happened |
|---|---|---|
| 401 | UNAUTHENTICATED |
Missing or invalid access token |
| 401 | INVALID_API_KEY |
Missing or invalid key |
| 403 | KEY_OWNER_MISMATCH |
The key doesn't belong to you |
| 403 | KEY_REVOKED |
This key has been revoked |
| 404 | NOT_FOUND |
Conversation not found or expired |
| 422 | (none) | Validation error (check the message) |
| 429 | BUDGET_EXCEEDED |
Your budget is spent. Top up or wait for reset |
Limits
| What | Limit |
|---|---|
| Message length | 10,000 characters |
| Conversation expiry | 24 hours |
| Budget | Per key, you decide |