ChatAE API
Programmatic read access to your accounts, contacts, research, and alerts. No write or webhook-subscription endpoints are exposed in v1.
Overview
The ChatAE API is a REST API that provides read access to your sales research data. All endpoints are under the /api/v1 base path and return JSON responses.
Base URL
https://www.app.chatae.ai/api/v1Use this canonical host directly. The non-www host redirects; clients may drop Authorization when following a cross-host redirect.
Download Postman collectionImport it, set api_key in a private Postman environment, and run GET /me first. The file contains no credentials or customer data.
Authentication
All requests require a valid API key passed as a Bearer token in the Authorization header.
Authorization: Bearer ak_...API keys are created in Settings → API Keys within the ChatAE app. Each key is scoped to specific permissions:
| Scope | Description |
|---|---|
accounts:read | Read accounts, overviews, and research results |
contacts:read | Read contacts |
alerts:read | Read alerts and alert events |
If a key lacks a required scope, the API returns 403 Forbidden.
Organization Scope
Keys are permanently bound to the active organization when they are minted. If no organization is active, the key is personal and organization_id is null. Organization-admin keys can read all current members’ data; non-admin and personal keys read only the creator’s data. Switching organizations later does not change an existing key. Create a new key to change its context.
Membership and role are rechecked on every request. An organization key stops working if its creator leaves that organization. No organization header or session cookie is required. Keep keys on your server, never in client-side JavaScript, URLs, or shared examples.
Rate Limiting
The API allows 1,000 requests per minute per key creator, shared across their keys. Rate limit status is returned in the GET /me response.
When rate limited, the API returns HTTP 429. Back off before retrying. GET /me reports usage without incrementing the counter; if the rate-limit store is unavailable, it reports the full allowance. When rate limited, the response body is:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Max 1000 requests per minute."
}
}Pagination
Accounts, research, contacts, and alert events use pagination. GET /alerts returns its full accessible list without pagination. Use next_cursor only while has_more is true. The API uses two pagination styles:
Cursor-based (Accounts, Research Results)
Pass starting_after with the ID of the last item from the previous page.
GET /api/v1/accounts?limit=50&starting_after=550e8400-e29b-41d4-a716-446655440000Page-based (Contacts, Alert Events)
Pass page and page_size parameters.
GET /api/v1/contacts?page=2&page_size=50List Response Envelope
All list endpoints return this shape. data contains resource objects; next_cursor is a resource ID for cursor-based endpoints, a page-number string for page-based endpoints, or null when finished:
{
"data": [],
"has_more": false,
"next_cursor": null
}Errors
Error responses use standard HTTP status codes with a consistent JSON body:
{
"error": {
"code": "not_found",
"message": "Account not found"
}
}| Code | HTTP Status | Description |
|---|---|---|
unauthorized | 401 | Missing, invalid, or revoked API key; or creator no longer belongs to the bound organization |
forbidden | 403 | Key lacks required scope |
not_found | 404 | Resource does not exist or is not accessible |
invalid_request | 422 | Malformed request parameters |
rate_limited | 429 | Too many requests |
Endpoints
Returns information about the authenticated user, their API key, and current rate limit usage.
{
"user_id": "user_abc123",
"organization_id": "org_def456",
"organization_admin": true,
"key": {
"id": "pak_xyz",
"name": "Production Key",
"prefix": "ak_...abc",
"scopes": ["accounts:read", "contacts:read", "alerts:read"],
"created_at": "2025-01-15T10:30:00.000Z",
"last_used_at": "2025-03-01T14:22:00.000Z"
},
"rate_limit": {
"limit": 1000,
"remaining": 994,
"window": "1m"
}
}List accounts with accessible, non-archived research results, ordered by ID, with optional name/domain search and cursor-based pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
qoptional | string | Search by account name or domain |
limitoptional | integer | Number of results (1-500, default 50) |
starting_afteroptional | string | Cursor: account ID to start after |
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp",
"domain": "acme.com",
"industry": "SaaS",
"location": "San Francisco, CA",
"logo_url": "https://...",
"folder": "Enterprise",
"tier": "tier_1",
"research_result_count": 12,
"created_at": "2025-01-10T08:00:00.000Z"
}
],
"has_more": true,
"next_cursor": "550e8400-e29b-41d4-a716-446655440000"
}Get a single account by ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | Account ID (e.g. 550e8400-e29b-41d4-a716-446655440000) |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corp",
"domain": "acme.com",
"industry": "SaaS",
"location": "San Francisco, CA",
"logo_url": "https://...",
"folder": "Enterprise",
"tier": "tier_1",
"research_result_count": 12,
"created_at": "2025-01-10T08:00:00.000Z"
}Read an existing AI-generated overview. Prefers the key creator’s overview; otherwise returns the most recently updated accessible overview. Returns 404 if none is available. This request does not generate research.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | Account ID |
{
"id": "acov_xyz789",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"summary": "Acme Corp is a fast-growing SaaS company...",
"sections": {
"keyInsights": [{"title": "Growth", "detail": "The company is expanding."}],
"opportunities": [],
"challenges": [],
"recentNews": [],
"talkingPoints": []
},
"version": 3,
"created_at": "2025-02-01T12:00:00.000Z",
"updated_at": "2025-03-01T09:00:00.000Z"
}List research results for an account. Each result contains the research query, response text, and source citations.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | Account ID |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limitoptional | integer | Number of results (1-500, default 50) |
starting_afteroptional | string | Cursor: research result ID to start after |
updated_afteroptional | string (ISO 8601) | Only return results updated after this timestamp |
{
"data": [
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Recent Funding & Growth",
"query": "What recent funding has Acme Corp raised?",
"text": "Acme Corp raised a $50M Series C in January 2025...",
"sources": [
{
"url": "https://techcrunch.com/...",
"title": "Acme raises $50M",
"source_type": "web"
}
],
"created_at": "2025-02-01T12:00:00.000Z",
"updated_at": "2025-03-01T09:00:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}List all contacts with filtering, search, and page-based pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
qoptional | string | Search by name, email, or company |
account_idoptional | string | Filter to contacts belonging to this account |
updated_afteroptional | string (ISO 8601) | Only contacts updated after this timestamp |
pageoptional | integer | Page number (default 1) |
page_sizeoptional | integer | Results per page (1-500, default 50) |
{
"data": [
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"name": "Jane Smith",
"first_name": "Jane",
"last_name": "Smith",
"title": "VP of Engineering",
"email": "jane@acme.com",
"email_status": "Valid",
"mobile": "+1-555-123-4567",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"account_name": "Acme Corp",
"domain": "acme.com",
"linkedin_url": "https://linkedin.com/in/janesmith",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"influence_role": "economic_buyer",
"sentiment": "positive",
"created_at": "2025-01-20T16:00:00.000Z",
"updated_at": "2025-03-01T10:00:00.000Z"
}
],
"has_more": true,
"next_cursor": "2"
}Get a single contact by ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | Contact ID (e.g. 770e8400-e29b-41d4-a716-446655440000) |
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"name": "Jane Smith",
"first_name": "Jane",
"last_name": "Smith",
"title": "VP of Engineering",
"email": "jane@acme.com",
"email_status": "Valid",
"mobile": "+1-555-123-4567",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"account_name": "Acme Corp",
"domain": "acme.com",
"linkedin_url": "https://linkedin.com/in/janesmith",
"city": "San Francisco",
"state": "CA",
"country": "United States",
"influence_role": "economic_buyer",
"sentiment": "positive",
"created_at": "2025-01-20T16:00:00.000Z",
"updated_at": "2025-03-01T10:00:00.000Z"
}List all configured alerts visible to this key. This endpoint is not paginated: has_more is false and next_cursor is null.
{
"data": [
{
"id": "alrt_jkl012",
"name": "Funding Announcements",
"query": "new funding round OR series raised",
"cadence": "daily",
"status": "active",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"account_name": "Acme Corp",
"account_domain": "acme.com",
"contact_id": null,
"contact_name": null,
"email_enabled": true,
"slack_channel_name": "#sales-alerts",
"event_count": 7,
"last_event_at": "2025-02-28T18:00:00.000Z",
"created_at": "2025-01-05T09:00:00.000Z",
"updated_at": "2025-02-28T18:00:00.000Z"
}
],
"has_more": false,
"next_cursor": null
}Get a single alert by ID, including delivery configuration and notes.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
idrequired | string | Alert ID (e.g. alrt_jkl012) |
{
"id": "alrt_jkl012",
"name": "Funding Announcements",
"query": "new funding round OR series raised",
"cadence": "daily",
"status": "active",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"account_name": "Acme Corp",
"account_domain": "acme.com",
"contact_id": null,
"contact_name": null,
"email_enabled": true,
"slack_channel_name": "#sales-alerts",
"event_count": 7,
"last_event_at": "2025-02-28T18:00:00.000Z",
"notes": "Track all target accounts for funding signals",
"created_at": "2025-01-05T09:00:00.000Z",
"updated_at": "2025-02-28T18:00:00.000Z"
}List alert events (signal detections) across all alerts, with filtering and page-based pagination.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
alert_idoptional | string | Filter to events from a specific alert |
account_idoptional | string | Filter by the parent alert’s account ID |
contact_idoptional | string | Filter by the parent alert’s contact ID |
event_typeoptional | string | Filter by type: event (default), completion, or error |
qoptional | string | Case-insensitive substring search across event title, summary, and alert name |
updated_afteroptional | string (ISO 8601) | Events created at or after this timestamp (uses the event record creation time) |
pageoptional | integer | Page number (default 1) |
page_sizeoptional | integer | Use 1-100 (default 50). Values above 100 currently cause incomplete pagination. |
{
"data": [
{
"id": "aevt_mno345",
"alert_id": "alrt_jkl012",
"alert_name": "Funding Announcements",
"event_type": "event",
"title": "Acme Corp raises $50M Series C",
"summary": "Acme Corp announced a $50M Series C led by...",
"event_date": "2025-02-28T12:00:00.000Z",
"source_urls": ["https://techcrunch.com/..."],
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"account_name": "Acme Corp",
"contact_id": null,
"contact_name": null,
"relevance": "New funding may create budget for the seller’s offering.",
"suggested_actions": [
{
"id": "act_pqr",
"label": "Draft congrats email",
"action_type": "send_email"
}
]
}
],
"has_more": true,
"next_cursor": "2"
}Quick Start
cURL
# Set CHATAE_API_KEY in your server or terminal environment. Do not commit it.
# Confirm the key and organization
curl --fail-with-body -H "Authorization: Bearer $CHATAE_API_KEY" \
"https://www.app.chatae.ai/api/v1/me"
# Search accessible accounts
curl --fail-with-body -H "Authorization: Bearer $CHATAE_API_KEY" \
"https://www.app.chatae.ai/api/v1/accounts?q=acme&limit=50"
# Set ACCOUNT_ID to an actual ID from the accounts response
curl --fail-with-body -H "Authorization: Bearer $CHATAE_API_KEY" \
"https://www.app.chatae.ai/api/v1/accounts/$ACCOUNT_ID/research"
curl --fail-with-body -H "Authorization: Bearer $CHATAE_API_KEY" \
"https://www.app.chatae.ai/api/v1/contacts?account_id=$ACCOUNT_ID"
# Read detected events safely; request later pages while has_more is true
curl --fail-with-body -H "Authorization: Bearer $CHATAE_API_KEY" \
"https://www.app.chatae.ai/api/v1/alert-events?page=1&page_size=100&event_type=event"JavaScript / TypeScript
// Server-side JavaScript (Node.js 18+) / TypeScript.
// Keep the API key on your server, never in browser code.
async function fetchAllAccounts(apiKey) {
if (!apiKey) throw new Error("CHATAE_API_KEY is required");
const accounts = [];
const seenCursors = new Set();
let cursor = null;
while (true) {
const url = new URL("https://www.app.chatae.ai/api/v1/accounts");
url.searchParams.set("limit", "500");
if (cursor) url.searchParams.set("starting_after", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!res.ok) {
// Handle 429 with backoff before retrying; never treat errors as data.
throw new Error(`ChatAE API returned HTTP ${res.status}`);
}
const json = await res.json();
if (!Array.isArray(json.data) || typeof json.has_more !== "boolean") {
throw new Error("Invalid list response");
}
accounts.push(...json.data);
if (!json.has_more) break;
if (typeof json.next_cursor !== "string" || !json.next_cursor || seenCursors.has(json.next_cursor)) {
throw new Error("Missing or repeated pagination cursor");
}
cursor = json.next_cursor;
seenCursors.add(cursor);
}
return accounts;
}
// Usage in your server: await fetchAllAccounts(process.env.CHATAE_API_KEY);Python
# Install the requests package in your environment first.
# Fetch ALL contacts updated since the same UTC cutoff.
import os
import requests
from datetime import datetime, timedelta, timezone
API_KEY = os.environ["CHATAE_API_KEY"]
BASE = "https://www.app.chatae.ai/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
since = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
contacts = []
page = 1
while True:
resp = requests.get(
f"{BASE}/contacts",
headers=headers,
params={"updated_after": since, "page_size": 500, "page": page},
timeout=30,
allow_redirects=False,
)
resp.raise_for_status()
if resp.status_code != 200:
raise RuntimeError(f"Unexpected HTTP status: {resp.status_code}")
body = resp.json()
if not isinstance(body.get("data"), list) or not isinstance(body.get("has_more"), bool):
raise RuntimeError("Invalid list response")
contacts.extend(body["data"])
if not body["has_more"]:
break
next_page = int(body.get("next_cursor") or 0)
if next_page <= page:
raise RuntimeError("Missing or repeated pagination cursor")
page = next_page
print(f"Found {len(contacts)} recently updated contacts")