API Β· v1
API Reference
Users, history, schema, prompts, and versioned KV.
Coding agents, refer to: llms.txt
Overview
TeamSquared is based on a flexible, spreadsheet-like data model. The API exposes the data schema (columns), views (sheets), and users (rows in the sheet, each one a user of AI), plus other system resources.
Bearer tokens are granted permission scopes and bound to a view.
Base URL
https://api.teamsquared.ai/api/v1
Version
Current: 1.0.0 (path: /v1). Breaking changes bump the path segment; non-breaking changes bump semver. See the changelog.
curl https://api.teamsquared.ai/api/v1/users \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
Authentication
Bearer token in the Authorization header. Tokens are 64 chars prefixed ext_agent_.
Create a token
- Log in to app.teamsquared.ai.
- Open Settings β API Tokens.
- Click New token, name it, pick the scopes and bound view, then create.
- Copy the full token immediately β see the warning below.
ext_agent_REPLACE_ME below is a placeholder.Authorization: Bearer ext_agent_REPLACE_MEheader
Scopes
Tokens carry one or more case-sensitive resource:permission scopes that gate which endpoints they can call. Scopes are picked in the UI when the token is created (Settings β API Tokens β New token) and can't be changed afterward β issue a new token to widen or narrow access. Missing scope β 403.
| Scope | Grants | v1 endpoints |
|---|---|---|
| users:read | Read users and per-user history. | GET /users, GET /users/:userId/history, GET /users/:userId/chat-messages |
| users:write | Create/update users, append history. | POST /users, PATCH /users/:userId, POST /users/:userId/history |
| schema:read | Read schema metadata. | GET /user_schema |
| schema:write | Create/update schema fields. | POST /user_schema, PATCH /user_schema/:fieldName |
| prompts:read | Resolve active prompts. | POST /prompts/resolve |
| prompts:write | Reserved. | No v1 endpoint yet. |
| kv:read | Read KV. | GET /kv, GET /kv/:key |
| kv:write | Write/delete KV. | PUT /kv/:key, DELETE /kv/:key |
{
"error": "Forbidden",
"message": "Missing required scope: users:write"
}403 responseUsers
Each row in the spreadsheet, whether it's a lead, contact, customer, invoice, ticket, etc is (or represents) a unique user of the AI system. A continuous history record is stored for each user.
In TeamSquared, almost everything is a user. A "user" row can represent any of:
- Live β a real production user of the AI system.
- Sandbox β a synthetic user created for experimentation and prompt iteration.
- Discussion β an example user that anchors a discussion, ticket, or escalation thread.
- Eval β an example user used as a training or evaluation case.
- Rated β a snapshot captured from a thumbs-up / thumbs-down rating.
This unified shape lets the platform freely clone, replay, and transport conversations between modes for AI improvement: a live conversation can be cloned into a sandbox to iterate on a fix, promoted into an eval to lock in expected behavior, or anchored into a discussion to track an escalation.
User type is exposed as userType on every record. Tokens (and webhooks) can opt into the user types they care about β see Events & user types.
Data model & views
Similar to a spreadsheet, the data model is flexible. Views (shown in the left sidebar of the dashboard below) define a filtered subset of users with limited data for a specific purpose. Every API token is bound to a view; the view determines which fields are returned on reads and which fields callers can filter or sort by.
What reads return
- Always returned β
id,recipientId,createdAt,lastResponseAt,lastUpdatedAt. - View fields β the view's curated list.
- Reserved fields β never returned, regardless of view.
:write token can mutate any user/schema field under its agent β view filters do not gate writes. Enforce narrower auth in your integration if needed.curl "https://api.teamsquared.ai/api/v1/users?sortBy=wellness_score&sortOrder=desc" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl Β· list users in view
Assuming the Bearer token is bound to the view shown.
// View exposes: name, wellness_goal, program_type, wellness_score
{
"users": [
{
"id": 103,
"recipientId": "+15125550103",
"createdAt": "2025-09-14T08:12:04.118Z",
"lastResponseAt": "2026-04-29T17:05:22.901Z",
"lastUpdatedAt": "2026-04-30T11:18:43.220Z",
"name": "Juan HernΓ‘ndez",
"wellness_goal": "nutrition",
"program_type": "6-month",
"wellness_score": 97
},
// ...7 more users
],
"totalCount": 8,
"pagination": {
"limit": 50,
"offset": 0,
"hasMore": false
}
}200 responseDetecting changes
lastUpdatedAt bumps on every mutation. Two patterns:
- Poll-and-diff β track the last
lastUpdatedAtper user; skip when unchanged. - Incremental sync β pass
?updatedSince=<iso>, then save the maxlastUpdatedAtfrom the batch for next loop.
Code example β ERP sync (Node, ~60 lines)
Mirrors users + chat history into a stand-in ERP. Initial offset-paged load, then a single ?updatedSince= catch-up. Read-only token.
const TOKEN = process.env.TOKEN;
const API = 'https://api.teamsquared.ai/api/v1';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Stand-in for the ERP's own table. The ERP β not the API β
// owns the record of when it last saved each user.
const erp = {
records: new Map(),
save(u) {
this.records.set(u.id, {
recipientId: u.recipientId,
name: u.name ?? null,
savedLastUpdatedAt: u.lastUpdatedAt,
});
},
newestSavedAt() {
return [...this.records.values()]
.map((r) => r.savedLastUpdatedAt).sort().at(-1);
},
};
async function initialSync() {
const limit = 20;
let offset = 0;
while (true) {
const url = new URL(`${API}/users`);
url.searchParams.set('limit', String(limit));
url.searchParams.set('offset', String(offset));
const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
const { users, pagination } = await res.json();
for (const u of users) {
erp.save(u);
const mres = await fetch(`${API}/users/${u.id}/chat-messages?limit=100`,
{ headers: { Authorization: `Bearer ${TOKEN}` } });
const { messages } = await mres.json();
console.log(JSON.stringify({ user: u, chatMessages: messages }, null, 2));
await sleep(1000); // stay under 60 req/min
}
if (!pagination.hasMore) break;
offset += limit;
await sleep(1000);
}
}
// In production, fired by a webhook subscription on user.updated:
// the ERP exposes an HTTP handler, the API POSTs the change, and
// the handler calls periodicSync(). Polling is a fallback.
async function periodicSync() {
const since = erp.newestSavedAt();
const url = new URL(`${API}/users`);
url.searchParams.set('updatedSince', since);
url.searchParams.set('limit', '200');
const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
const { users } = await res.json();
const changed = [];
for (const u of users) {
const existing = erp.records.get(u.id);
if (existing && existing.savedLastUpdatedAt === u.lastUpdatedAt) continue;
erp.save(u);
changed.push(u);
}
console.log(JSON.stringify({ changed }, null, 2));
}
await initialSync();
await periodicSync();sync-poc.mjscurl --get "https://api.teamsquared.ai/api/v1/users" \ --data-urlencode "updatedSince=2026-05-01T00:00:00Z" \ --data-urlencode "limit=200" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl Β· incremental sync
Rate limiting
60 req/min/token, fixed-window. Per-token, not per-IP.
On exceed: 429 with Retry-After. Please honor it.
HTTP/1.1 429 Too Many Requests
Retry-After: 47
Content-Type: application/json
{
"error": "Too Many Requests",
"message": "Rate limit exceeded: 60 req/min"
}429 responseErrors
| Status | Meaning | Triggered by |
|---|---|---|
| 200 | OK | Read or update succeeded |
| 201 | Created | Resource created |
| 400 | Bad Request | Missing fields, bad params, filter/sort outside view |
| 401 | Unauthorized | Missing or invalid token |
| 403 | Forbidden | Missing scope |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate (e.g. existing recipientId) |
| 429 | Too Many Requests | Rate limit β see Rate limiting |
| 500 | Internal Server Error | Server error |
JSON body with error, often message, plus context (recipientId, fieldName, β¦).
{
"error": "User not found",
"recipientId": "+1234567890"
}404 response{
"error": "Bad Request",
"message": "Filter field not in view: selected_office"
}400 responseUsers
All under /api/v1/users. Single-user lookup: filter the list by recipientId (no by-ID route).
recipientId is the primary channel address (phone, WhatsApp, etc.) β a cross-platform customer key. Unique per agent. Inbound webhooks resolve against it.List users
/api/v1/users
Paginated, view-scoped list with filters and sort. Single-user fetch: filter by recipientId.
Required scope users:read
Query parameters
| Parameter | Type | Description |
|---|---|---|
| limit | number | Default 50, max 200 (clamped). |
| offset | number | Default 0. Must be a multiple of limit. |
| sortBy | string | Defaults to view's sortBy, then lastUpdatedAt. Must be in the view. |
| sortOrder | asc | desc | Defaults to view's, then desc. |
| filters | JSON Filter[] | URL-encoded. AND'd on top of view filters. See operators. |
| logicalOperator | and | or | Composes caller filters. Default and. |
| updatedSince | ISO 8601 | Shorthand for an incremental sync filter β see Detecting changes. |
| includeCount | boolean | Adds totalCount. Off by default β COUNT(*) is expensive. |
Errors
- 400
Filter field not in view: <field> - 400
offset must be a multiple of limit
curl --get "https://api.teamsquared.ai/api/v1/users" \ --data-urlencode "limit=50" \ --data-urlencode "offset=0" \ --data-urlencode "includeCount=true" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl Β· basic
FILTERS='[{"field":"recipientId","operator":"eq","value":"+1234567890"}]'
curl --get "https://api.teamsquared.ai/api/v1/users" \
--data-urlencode "filters=${FILTERS}" \
--data-urlencode "limit=1" \
-H "Authorization: Bearer ext_agent_REPLACE_ME"
# Returns { "users": [user] } on hit, { "users": [] } on miss.curl Β· single-user lookupFILTERS='[{"field":"status","operator":"eq","value":"active"},
{"field":"name","operator":"like","value":"Smith"}]'
curl --get "https://api.teamsquared.ai/api/v1/users" \
--data-urlencode "filters=${FILTERS}" \
--data-urlencode "limit=50" \
-H "Authorization: Bearer ext_agent_REPLACE_ME"curl Β· with filters{
"users": [
{
"id": 137,
"recipientId": "+1234567890",
"createdAt": "2025-10-31T02:37:58.753Z",
"lastResponseAt": null,
"lastUpdatedAt": "2026-05-01T10:42:11.005Z",
"name": "John Doe",
"appointment_time": "2026-05-02T10:00:00Z"
}
],
"totalCount": 137,
"pagination": {
"limit": 50,
"offset": 0,
"hasMore": true
}
}200 responseCreate user
/api/v1/users
Create a user. Extra keys are stored as custom data.
Required scope users:write
Body
| Field | Type | Description |
|---|---|---|
| recipientIdreq | string | Phone or unique channel address. |
| name | string | Display name. |
| lastResponseAt | ISO 8601 | Seed the inferred last-response timestamp. |
| [any other key] | any | Custom data. Must be in the bound view. |
Errors
- 400
recipientIdmissing - 409 User with that
recipientIdexists
curl -X POST "https://api.teamsquared.ai/api/v1/users" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"recipientId": "+1234567890",
"name": "John Doe",
"selected_office": "Manhattan",
"appointment_time": "2025-11-01T10:00:00Z"
}'curl{
"user": {
"id": 137,
"recipientId": "+1234567890",
"createdAt": "2025-10-31T02:37:58.753Z",
"lastResponseAt": null,
"lastUpdatedAt": "2025-10-31T02:37:58.753Z",
"name": "John Doe",
"selected_office": "Manhattan",
"appointment_time": "2025-11-01T10:00:00Z"
}
}201 responseUpdate user
/api/v1/users/:userId
Custom fields are merged β send only what changed.
Required scope users:write
lastUpdatedAt bumps on every write. Writes are agent-scoped β the bound view does not restrict which users can be updated.Body (all optional)
| Field | Type | Description |
|---|---|---|
| name | string | Display name. |
| lastResponseAt | ISO 8601 | Override the inferred last-response timestamp. |
| [any other key] | any | Merged into custom data. Must be in the bound view. |
curl -X PATCH "https://api.teamsquared.ai/api/v1/users/137" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"appointment_confirmed": true
}'curl{
"user": {
"id": 137,
"recipientId": "+1234567890",
"createdAt": "2025-10-31T02:37:58.753Z",
"lastResponseAt": null,
"lastUpdatedAt": "2025-10-31T02:42:11.005Z",
"name": "John Doe",
"appointment_time": "2025-11-01T10:00:00Z",
"appointment_confirmed": true
}
}200 responseHistory
Per-user event log β tool runs, state changes, messages. Read raw or as LLM-shaped chat messages.
Add history entry
/api/v1/users/:userId/history
Append an event entry.
Required scope users:write
Body
| Field | Type | Description |
|---|---|---|
| eventreq | string | Event type, e.g. user_message, appointment_scheduled. |
| messagereq | string | Human-readable summary. |
curl -X POST \
"https://api.teamsquared.ai/api/v1/users/137/history" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"event": "appointment_scheduled",
"message": "User scheduled appointment for Nov 1, 2025"
}'curl{
"history": {
"id": 1172,
"userId": 137,
"event": "appointment_scheduled",
"message": "User scheduled appointment for Nov 1, 2025",
"createdAt": "2025-10-31T02:35:50.315Z"
}
}201 responseList history
/api/v1/users/:userId/history
Paginated history with optional event and recency filters.
Required scope users:read
Query parameters
| Parameter | Type | Description |
|---|---|---|
| limit | number | Default 100. |
| offset | number | Default 0. |
| event | string | Exact match. |
| sinceSeconds | number | Last N seconds (e.g. 86400 = 24h). |
curl --get "https://api.teamsquared.ai/api/v1/users/137/history" \ --data-urlencode "limit=50" \ --data-urlencode "event=user_message" \ --data-urlencode "sinceSeconds=86400" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"history": [
{
"id": 1172,
"userId": 137,
"event": "user_message",
"message": "Hello, I need help",
"createdAt": "2025-10-31T02:35:50.315Z"
},
{
"id": 1173,
"userId": 137,
"event": "assistant_message",
"message": "Hi! How can I help you today?",
"createdAt": "2025-10-31T02:35:51.420Z"
}
],
"totalCount": 3,
"pagination": { "limit": 50, "offset": 0, "hasMore": false }
}200 responseGet chat messages
/api/v1/users/:userId/chat-messages
History flattened to { role, content } β drop into a prompt.
Required scope users:read
user/assistantroles only.contentis always a string; complex content is JSON-stringified.
curl "https://api.teamsquared.ai/api/v1/users/137/chat-messages" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"messages": [
{ "role": "user", "content": "Hello, I need help" },
{ "role": "assistant", "content": "Hi! How can I help you today?" },
{ "role": "user", "content": "I want to schedule an appointment" },
{ "role": "assistant", "content": "Sure β what date works for you?" }
]
}200 responseUser schema
Built-in fields plus any custom fields you define. Custom fields are exposed via the bound view.
List schema fields
/api/v1/user_schema
View-exposed custom fields. UI/admin metadata (icons, render types) is not part of the API.
Required scope schema:read
curl "https://api.teamsquared.ai/api/v1/user_schema" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"fields": [
{ "fieldName": "name", "type": "string" },
{ "fieldName": "selected_office", "type": "string",
"enum": ["Manhattan", "Brooklyn", "Queens"] },
{ "fieldName": "appointment_time", "type": "string",
"format": "date-time" }
]
}200 responseCreate field
/api/v1/user_schema
Add a custom field. Built-in fields aren't creatable via the API.
Required scope schema:write
Body
| Field | Type | Description |
|---|---|---|
| fieldNamereq | string | snake_case identifier. |
| typereq | enum | string Β· number Β· boolean |
| enum | string[] | Allowed values. |
| format | string | e.g. date-time. |
Errors
- 409 Field already exists
curl -X POST "https://api.teamsquared.ai/api/v1/user_schema" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"fieldName": "insurance_provider",
"type": "string"
}'curl{
"field": {
"fieldName": "insurance_provider",
"type": "string"
}
}201 responseUpdate field
/api/v1/user_schema/:fieldName
Update metadata. fieldName and type are immutable β to rename or retype, create a new field and migrate.
Required scope schema:write
curl -X PATCH \
"https://api.teamsquared.ai/api/v1/user_schema/insurance_provider" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"enum": ["Blue Cross", "Aetna", "UnitedHealth", "Self-Pay"]
}'curl{
"field": {
"fieldName": "insurance_provider",
"type": "string",
"enum": ["Blue Cross", "Aetna", "UnitedHealth", "Self-Pay"]
}
}200 responsePrompts
Versioned prompt strings keyed by promptType. Resolves to the agent's active version with optional {{placeholder}} interpolation.
Resolve prompt
/api/v1/prompts/resolve
Placeholders: {{fieldName}}, {{chatHistory}}, {{now}}.
Required scope prompts:read
Body
| Field | Type | Description |
|---|---|---|
| promptTypereq | string | e.g. my-agent. |
| adminUserId | string | User-specific selection during admin testing. |
| interpolate | boolean | Inline sub-prompts. Default false. |
curl -X POST "https://api.teamsquared.ai/api/v1/prompts/resolve" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"promptType": "my-agent",
"interpolate": true
}'curl{
"prompt": {
"id": 42,
"type": "my-agent",
"content": "You are a helpful assistant...",
"version": "v2.1",
"isActive": true,
"createdAt": "2025-10-15T10:00:00.000Z",
"updatedAt": "2025-10-30T14:30:00.000Z"
},
"meta": {
"promptVersions": [
{
"trackingField": "prompt_version_my_agent",
"trackingValue": "v2.1"
}
]
}
}200 responseKV storage
Version-scoped key/value store. Defaults to the live version; pass ?versionId=<id> to target another.
List KV pairs
/api/v1/kv
All KV pairs for the version.
Required scope kv:read
| Parameter | Type | Description |
|---|---|---|
| versionId | number | Defaults to live. |
curl "https://api.teamsquared.ai/api/v1/kv" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"entries": [
{
"key": "apiKey",
"value": "sk-...",
"createdAt": "2025-12-01T10:00:00.000Z",
"updatedAt": "2025-12-01T10:00:00.000Z"
},
{
"key": "debugMode",
"value": true,
"createdAt": "2025-12-01T10:00:00.000Z",
"updatedAt": "2025-12-01T10:00:00.000Z"
}
]
}200 responsecurl "https://api.teamsquared.ai/api/v1/kv/apiKey" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"key": "apiKey",
"value": "sk-...",
"createdAt": "2025-12-01T10:00:00.000Z",
"updatedAt": "2025-12-01T10:00:00.000Z"
}200 responsecurl -X PUT "https://api.teamsquared.ai/api/v1/kv/debugMode" \
-H "Authorization: Bearer ext_agent_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"value": true}'curl{
"success": true,
"key": "debugMode"
}200 responsecurl -X DELETE "https://api.teamsquared.ai/api/v1/kv/debugMode" \ -H "Authorization: Bearer ext_agent_REPLACE_ME"curl
{
"success": true,
"key": "debugMode"
}200 responseWebhooks
Get notified in your own systems when something changes inside TeamSquared. Each subscription POSTs a signed JSON envelope to a URL of your choosing.
How it works
When a subscribed event fires, TeamSquared queues a delivery, signs it with HMAC-SHA256, and POSTs it to your URL. Failures retry with exponential backoff.
Subscriptions are managed in the dashboard (agent superadmin only), not in the API.
GET /users and writes it to your system.
TeamSquared Your service
βββββββββββ ββββββββββββ
event fires (e.g. user.updated)
β enqueue outbound row
β cron processor (~1 min)
β POST {url} βββΆ verify X-Webhook-Signature
process payload
respond 2xx
β 2xx β mark sent
β non-2xx / timeout β backoff & retryflowCreate a subscription
Settings β Webhooks β Create Webhook. Set:
- Name β internal label.
- Destination URL β HTTPS endpoint that will receive deliveries.
- Events β one or more (see below).
- User Types β which user types fire this webhook. Defaults to
Live.
The signing secret (whsec_β¦) is shown once. Copy it immediately β it can't be retrieved later.
Row β― menu: Send test event, Pause / Resume, Delete.
Settings
βββ Webhooks
βββ Create Webhook
βββ Name: CRM sync
βββ Destination URL: https://hooks.example.com/teamsquared
βββ Events:
β β User created
β β User updated
β β User deleted
β β Chat message created
βββ User Types:
β Live
β Sandbox
β Discussion
β Eval
β Ratedcreate flowEvents & user types
Fires only when event matches and the user's type is in the subscription's User Types list.
| Event | Fires when |
|---|---|
| user.created | A new user is added. |
| user.updated | Any field on a user changes. |
| user.deleted | A user is deleted. |
| chat.message.created | A chat message is recorded β inbound (user) or outbound (agent). |
Example: a subscription on user.updated with User Types = [live, sandbox] will fire when a sandbox user is edited, but a "live"-only subscription will not.filter rules
Payload
POST, Content-Type: application/json, three signing headers:
| Header | Value |
|---|---|
| X-Webhook-Event | Event type, e.g. user.updated. |
| X-Webhook-Id | Unique delivery id β use for at-least-once dedupe. |
| X-Webhook-Signature | sha256=<hex> β HMAC-SHA256 of the raw body using your secret. |
data is intentionally minimal β fetch full state from the API if you need it.
{
"eventType": "user.updated",
"occurredAt": "2026-05-04T17:30:00.123Z",
"webhookId": 42,
"agentId": 1,
"workspaceId": 1,
"data": {
"id": 137,
"recipientId": "+1234567890",
"userType": "live",
"createdAt": "2025-10-31T02:37:58.753Z",
"lastUpdatedAt": "2026-05-04T17:30:00.005Z"
}
}envelopeReceiving & verifying
Drop-in Node.js / Express handler. Verifies the signature in constant time, then processes the event.
- Use
express.raw()β JSON-parsing changes byte order and breaks the signature. - Compare with
timingSafeEqualto avoid timing attacks. - Return 2xx within 10 s. Defer slow work.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.post(
'/webhooks/teamsquared',
express.raw({ type: 'application/json' }),
(req, res) => {
if (!verifySignature(req.body, req.header('x-webhook-signature'))) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
// event.eventType, event.data.id
// req.header('x-webhook-id') β dedupe key
res.status(200).end(); // ack fast; defer work
}
);
function verifySignature(rawBody, header) {
if (!header) return false;
const expected = 'sha256=' +
crypto.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}handler.jsRetries & testing
Any 2xx acknowledges (body ignored). Anything else retries with exponential backoff: 30 s β 1 min β 2 min β 4 min β β¦ capped at 24 h, up to 8 attempts. After that the event stays Failed.
Each delivery times out after 10 seconds.
Send test event (row β― menu) fires a synthetic webhook.test envelope synchronously and reports the response status in a toast.
// Test event envelope
{
"eventType": "webhook.test",
"occurredAt": "2026-05-04T17:30:00.123Z",
"webhookId": 42,
"agentId": 1,
"workspaceId": 1,
"data": {
"message": "This is a test event from TeamSquared."
}
}test envelopeReference
Type definitions
User
type User = {
// Always returned
id: number;
recipientId: string;
createdAt: string; // ISO 8601
lastResponseAt: string | null;
lastUpdatedAt: string; // ISO 8601 β bumps on every write
// Additional fields exposed by the token's bound view.
[key: string]: any;
};
type CreateUserRequest = {
recipientId: string;
name?: string | null;
lastResponseAt?: string; // ISO 8601
// Additional JSONB fields β must be in the bound view.
// Internal columns (status, priority, userType, ...) return 400.
[key: string]: any;
};
type UpdateUserRequest = {
name?: string | null;
lastResponseAt?: string; // ISO 8601
[key: string]: any;
};typescriptHistory
type HistoryEntry = {
id: number;
userId: number;
event: string;
message: string;
createdAt: string;
};
type ChatMessage = {
role: 'user' | 'assistant';
content: string;
};typescriptSchema
type UserSchemaField = {
fieldName: string;
type: 'string' | 'number' | 'boolean';
enum?: string[];
format?: string; // e.g. 'date-time'
};typescriptKV
type KVEntry = {
key: string;
value: any; // Any JSON-serializable value
createdAt: string;
updatedAt: string;
};
type SetKVRequest = { value: any };
type KVOperationResponse = { success: boolean; key: string };typescriptFilter operators
JSON arrays of { field, operator, value }. Caller filters combine via logicalOperator (default and), then AND with the view's filters.
| Operator | Use |
|---|---|
| eq | Exact match. Strings case-sensitive. |
| like | Case-insensitive substring. |
| gt | Greater than (numbers, dates). |
| lt | Less than. |
| since | β₯ ISO timestamp. The updatedSince shortcut translates to this. |
| until | β€ ISO timestamp. |
| is_null | Field is NULL. value ignored. |
| is_not_null | Field is not NULL. value ignored. |
[
{ "field": "status", "operator": "eq", "value": "active" },
{ "field": "lastUpdatedAt", "operator": "since", "value": "2026-05-01T00:00:00Z" },
{ "field": "priority", "operator": "gt", "value": 5 }
]filters Β· exampleChangelog
| Version | Date | Notes |
|---|---|---|
| v1.0.0 | 2026-03-09 | Initial release: users, history, schema, prompts, KV. |