Twin API Reference
A REST API for querying your AI twin, managing your knowledge base, and integrating Twin into any workflow.
https://api.twin.ai
Quick start
Get a response from your twin in under 2 minutes.
twin_live_.# Ask your twin a question curl -X POST https://api.twin.ai/v1/twin \ -H "Authorization: Bearer twin_live_..." \ -H "Content-Type: application/json" \ -d '{"message": "What is the expense policy?"}'
{
"response": "Under $50 no pre-approval needed. $50–$500 requires manager email approval before purchase. Submit via Expensify by the 20th.",
"confidence": 0.92,
"shouldEscalate": false,
"sources": ["Expense reimbursement policy"]
}
Authentication
All API requests must include your API key in the Authorization header.
Authorization: Bearer twin_live_sk_a8f2c4e1...
Errors
Twin uses standard HTTP status codes. All errors return a JSON body with an error field.
{
"error": "Message too long (max 2000 chars)."
}
error field for details.Retry-After header.Rate limits
Rate limits are per API key, per minute. Headers indicate your current status.
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 42 X-RateLimit-Reset: 1710423600 Retry-After: 30 # only present on 429 responses
Twin
Ask your twin a question. It searches your knowledge base, generates an answer, and tells you how confident it is.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | required | The question to ask your twin. Max 2,000 characters. |
| context | string | optional | Additional context to include alongside the KB (e.g. thread history). Max 4,000 chars. |
| askedBy | string | optional | Name of the person asking. Logged with escalations. Default: "API". |
curl -X POST https://api.twin.ai/v1/twin \ -H "Authorization: Bearer twin_live_..." \ -H "Content-Type: application/json" \ -d '{ "message": "What is the expense policy for a $200 SaaS tool?", "askedBy": "Jordan M." }'
{
"response": "Under $50 no pre-approval needed. $50–$500 requires manager email before purchase. Submit via Expensify by the 20th.",
"confidence": 0.92,
"shouldEscalate": false,
"escalateReason": null,
"sources": ["Expense reimbursement policy"]
}
{
"response": "This requires a personal decision — escalating to AK.",
"confidence": 0.21,
"shouldEscalate": true,
"escalateReason": "Requires personal budget authority over $10k",
"sources": []
}
HTTP/1.1 402 Payment Required { "error": "Monthly answer limit reached (500/500). Upgrade to Pro for 2,000 answers/month." }
| Field | Type | Description |
|---|---|---|
| response | string | The twin's answer in plain text. |
| confidence | float | 0.0–1.0. How well the KB supported the answer. Below your threshold → shouldEscalate is true. |
| shouldEscalate | boolean | True if the confidence is below your threshold or if the question requires personal judgment. |
| escalateReason | string | null | Human-readable reason for escalation. Null if shouldEscalate is false. |
| sources | string[] | Titles of KB documents that contributed to the answer. |
Knowledge Base
Create, list, and delete documents in your twin's knowledge base. Supports bulk imports and programmatic sync from external sources.
| Parameter | Type | Description |
|---|---|---|
| type | string | Filter by type: sop, policy, email, slack, drive, note |
| limit | integer | Max results to return. Default 50, max 200. |
curl https://api.twin.ai/v1/kb?type=policy \
-H "Authorization: Bearer twin_live_..."
{
"docs": [
{
"id": "doc_abc123",
"title": "Expense reimbursement policy",
"type": "policy",
"source": "manual",
"hits": 8,
"createdAt": "2026-03-01T10:00:00Z"
}
],
"total": 1
}
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | required | Document title. Shown in source citations. |
| content | string | required | The document content. Plain text. Max 50,000 characters. |
| type | string | optional | sop | policy | email | slack | drive | note. Default: sop. |
// Import all pages from your Notion workspace const pages = await getNotionPages() for (const page of pages) { await fetch('https://api.twin.ai/v1/kb', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ title: page.title, type: 'note', content: page.plainText, }), }) }
curl https://api.twin.ai/v1/kb/doc_abc123 \
-H "Authorization: Bearer twin_live_..."
{
"doc": {
"id": "doc_abc123",
"title": "Expense reimbursement policy",
"type": "policy",
"content": "Under $50 no pre-approval needed...",
"source": "manual",
"hits": 8,
"createdAt": "2026-03-01T10:00:00Z"
}
}
curl -X DELETE https://api.twin.ai/v1/kb/doc_abc123 \
-H "Authorization: Bearer twin_live_..."
HTTP/1.1 200 OK { "deleted": true }
Escalations
List and resolve escalations — questions your twin couldn't answer confidently.
| Parameter | Type | Description |
|---|---|---|
| status | string | open | resolved | all. Default: open. |
| limit | integer | Max results. Default 50, max 200. |
{
"escalations": [
{
"id": "esc_xyz789",
"question": "Can you approve the $15k contract with Acme?",
"askedBy": "Jordan M.",
"confidence": 0.22,
"reason": "Requires personal budget authority over $10k",
"status": "open",
"createdAt": "2026-03-14T10:32:00Z",
"resolvedAt": null
}
],
"total": 1
}
curl -X PATCH https://api.twin.ai/v1/escalations/esc_xyz789 \ -H "Authorization: Bearer twin_live_..." \ -H "Content-Type: application/json" \ -d '{"status": "resolved"}'
Stats
Usage statistics and performance metrics for your twin.
{
"totalAnswered": 284,
"totalEscalated": 34,
"avgConfidence": 0.81,
"handleRate": 0.88,
"totalSavedMinutes": 852,
"kbDocCount": 12
}
Webhooks
Receive real-time notifications when your twin escalates a question. Set your webhook URL in Settings → API & webhooks.
Twin sends a POST request to your webhook URL with this body whenever shouldEscalate is true.
{
"event": "escalation.created",
"escalationId": "esc_xyz789",
"question": "Can you approve the $15k contract with Acme?",
"askedBy": "Jordan M.",
"confidence": 0.22,
"reason": "Requires personal budget authority over $10k",
"timestamp": "2026-03-14T10:32:00Z"
}
Every webhook request includes a signature header. Verify it to ensure the request is from Twin.
Twin-Signature: sha256=abc123...
const crypto = require('crypto') function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex') return crypto.timingSafeEqual( Buffer.from(`sha256=${expected}`), Buffer.from(signature) ) }