Integrate
REST API
Your own systems can call the Core API with API keys created in the admin console. Every endpoint is listed in the API reference.
#Base URL
All endpoints live under a versioned path:
https://api.2people.chat/api/v1Request and response bodies are JSON (camelCase field names); a few upload endpoints take multipart/form-data, and export endpoints return CSV or plain text. Ids are UUIDs. Requests with fields that are not defined are rejected.
#Creating an API key
Open the API keys page
In the admin console go to your tenant’s API keys page (requires
settings:write).Choose a name, an expiry date and scopes
Scopes are tenant permissions (e.g.
conversations:read,analytics:read). You can only grant permissions you hold yourself.members:writeandroles:writecan never be given to a key; people and roles are managed in the console only. The expiry date is optional.Copy the secret
The key (
tpk_…) is shown only once; the platform stores only its hash. The list shows only the prefix (tpk_<prefix>), scopes and when it was last used.
#Authentication
Send two headers with every request:
| Header | Value |
|---|---|
Authorization | Bearer tpk_… |
X-Tenant-Id | The tenant id (UUID). It is in the address of the tenant page in the admin console (/tenants/<id>). Not needed on endpoints that carry the tenant in the path (e.g. /tenants/{id}). |
curl "https://api.2people.chat/api/v1/conversations?view=open&limit=25" \
-H "Authorization: Bearer $TWOPEOPLE_API_KEY" \
-H "X-Tenant-Id: $TWOPEOPLE_TENANT_ID"- Keys can only reach endpoints that require a permission; session and profile endpoints (such as
/me) and platform endpoints are closed to keys. - Revoked or expired keys, and keys of a suspended tenant, are rejected.
- The API is not meant to be called from browsers (CORS is open only to the platform’s own apps); use keys on your servers only.
#Errors
Errors use application/problem+json (RFC 9457). Every body carries a stable UPPER_CASE code and the request’s correlationId. Branch on code; title and detail texts may change.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
X-Correlation-Id: 3f2c9a7e-…
{
"type": "https://docs.2people.chat/problems/validation-failed",
"title": "Bad Request",
"status": 400,
"code": "VALIDATION_FAILED",
"correlationId": "3f2c9a7e-…",
"errors": [
{ "path": "limit", "message": "limit must not be greater than 100" }
]
}| HTTP | code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | Invalid or unknown field; details in errors[]. |
| 400 | TENANT_CONTEXT_REQUIRED | The X-Tenant-Id header is missing. |
| 401 | UNAUTHENTICATED | Key missing, invalid, expired or revoked. |
| 403 | FORBIDDEN | The key lacks the scope this endpoint needs, or the endpoint is not available to API keys. |
| 404 | TENANT_NOT_FOUND | The tenant does not exist or the key does not belong to it (both give the same answer). |
| 404 | ROUTE_NOT_FOUND | No such endpoint. |
| 409 | CONFLICT | Conflict (e.g. the value already exists). Some endpoints return more specific codes. |
| 429 | RATE_LIMITED | Rate limit exceeded. |
| 500 | INTERNAL | Unexpected error; report it with the correlationId. |
You can send your own X-Correlation-Id (8–128 characters: letters, digits and . _ : -); the same value comes back in the response and in error bodies. Otherwise the platform generates one.
#Pagination
List endpoints use cursor pagination: limit (1–100, default 25) and the opaque cursor from the previous response. Responses look like { items, nextCursor }; when nextCursor is null you are on the last page. Do not decode or build cursors yourself. The API reference shows which endpoints take these parameters.
const BASE = 'https://api.2people.chat/api/v1';
const headers = {
Authorization: `Bearer ${process.env.TWOPEOPLE_API_KEY}`,
'X-Tenant-Id': process.env.TWOPEOPLE_TENANT_ID,
};
// Walks every page of a cursor-paginated list.
async function* listAll(path, params = {}) {
let cursor;
do {
const query = new URLSearchParams({ ...params, limit: '100', ...(cursor ? { cursor } : {}) });
const res = await fetch(`${BASE}${path}?${query}`, { headers });
if (!res.ok) {
const problem = await res.json(); // application/problem+json
throw new Error(`${problem.code}: ${problem.detail ?? problem.title} (${problem.correlationId})`);
}
const page = await res.json();
yield* page.items;
cursor = page.nextCursor;
} while (cursor);
}
for await (const conversation of listAll('/conversations', { view: 'closed' })) {
console.log(conversation.id, conversation.status);
}#Rate limits
The default limit is 300 requests per minute per client IP address. Above it the API returns 429 with RATE_LIMITED; wait and retry with increasing delays.
#API reference
The method, path, required permission, parameters, request body and responses of every endpoint are in the API reference.