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/v1

Use this canonical host directly. The non-www host redirects; clients may drop Authorization when following a cross-host redirect.

Download Postman collection

Import 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:

ScopeDescription
accounts:readRead accounts, overviews, and research results
contacts:readRead contacts
alerts:readRead 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-446655440000

Page-based (Contacts, Alert Events)

Pass page and page_size parameters.

GET /api/v1/contacts?page=2&page_size=50
Alert events: use page_size of 100 or below. Larger values currently return only 100 rows but calculate has_more using the requested size, causing pagination to stop early. Verified August 31, 2026. Contacts still support page_size up to 500.

List 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"
  }
}
CodeHTTP StatusDescription
unauthorized401Missing, invalid, or revoked API key; or creator no longer belongs to the bound organization
forbidden403Key lacks required scope
not_found404Resource does not exist or is not accessible
invalid_request422Malformed request parameters
rate_limited429Too many requests

Endpoints

GET/me

Returns information about the authenticated user, their API key, and current rate limit usage.

Response
{
  "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"
  }
}
GET/accountsaccounts:read

List accounts with accessible, non-archived research results, ordered by ID, with optional name/domain search and cursor-based pagination.

Query Parameters

ParameterTypeDescription
qoptionalstringSearch by account name or domain
limitoptionalintegerNumber of results (1-500, default 50)
starting_afteroptionalstringCursor: account ID to start after
Response
{
  "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/accounts/:idaccounts:read

Get a single account by ID.

Path Parameters

ParameterTypeDescription
idrequiredstringAccount ID (e.g. 550e8400-e29b-41d4-a716-446655440000)
Response
{
  "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"
}
GET/accounts/:id/overviewaccounts:read

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

ParameterTypeDescription
idrequiredstringAccount ID
Response
{
  "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"
}
GET/accounts/:id/researchaccounts:read

List research results for an account. Each result contains the research query, response text, and source citations.

Path Parameters

ParameterTypeDescription
idrequiredstringAccount ID

Query Parameters

ParameterTypeDescription
limitoptionalintegerNumber of results (1-500, default 50)
starting_afteroptionalstringCursor: research result ID to start after
updated_afteroptionalstring (ISO 8601)Only return results updated after this timestamp
Response
{
  "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
}
GET/contactscontacts:read

List all contacts with filtering, search, and page-based pagination.

Query Parameters

ParameterTypeDescription
qoptionalstringSearch by name, email, or company
account_idoptionalstringFilter to contacts belonging to this account
updated_afteroptionalstring (ISO 8601)Only contacts updated after this timestamp
pageoptionalintegerPage number (default 1)
page_sizeoptionalintegerResults per page (1-500, default 50)
Response
{
  "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/contacts/:idcontacts:read

Get a single contact by ID.

Path Parameters

ParameterTypeDescription
idrequiredstringContact ID (e.g. 770e8400-e29b-41d4-a716-446655440000)
Response
{
  "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"
}
GET/alertsalerts:read

List all configured alerts visible to this key. This endpoint is not paginated: has_more is false and next_cursor is null.

Response
{
  "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/alerts/:idalerts:read

Get a single alert by ID, including delivery configuration and notes.

Path Parameters

ParameterTypeDescription
idrequiredstringAlert ID (e.g. alrt_jkl012)
Response
{
  "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"
}
GET/alert-eventsalerts:read

List alert events (signal detections) across all alerts, with filtering and page-based pagination.

Query Parameters

ParameterTypeDescription
alert_idoptionalstringFilter to events from a specific alert
account_idoptionalstringFilter by the parent alert’s account ID
contact_idoptionalstringFilter by the parent alert’s contact ID
event_typeoptionalstringFilter by type: event (default), completion, or error
qoptionalstringCase-insensitive substring search across event title, summary, and alert name
updated_afteroptionalstring (ISO 8601)Events created at or after this timestamp (uses the event record creation time)
pageoptionalintegerPage number (default 1)
page_sizeoptionalintegerUse 1-100 (default 50). Values above 100 currently cause incomplete pagination.
Response
{
  "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")