{
  "info": {
    "name": "ChatAE Public API v1",
    "description": "# ChatAE Public API\n\nRead-only requests for account research, contacts, and alerts.\n\n## Quick start\n1. Import this JSON file into Postman.\n2. Create/select a private environment and set api_key to your own ChatAE API key. Keep the value private; do not include it when sharing exports.\n3. Send Identity / Get current key info to inspect organization_id, organization_admin, and key.scopes.\n4. Run the collection in order to test the list and sample detail endpoints. Requires accounts:read, contacts:read, and alerts:read to test everything.\n\nAuthorization is inherited from the collection: Authorization: Bearer {{api_key}}. No cookies or organization header are required. The base URL is the canonical production hostname.\n\nList requests select sample IDs for later requests using temporary run-local variables only. When sending individual detail requests manually, set account_id, contact_id, or alert_id yourself. Requests missing an ID are skipped; a skipped request is not a passed test. An overview can return 404 if one has not been generated.\n\n## Pagination\nAccounts and research: if has_more is true, copy next_cursor into the corresponding cursor variable and enable the disabled starting_after parameter.\nContacts and alert events: if has_more is true, copy next_cursor into contacts_page or alert_events_page.\nReset page variables to 1 and disable starting_after to restart. A normal collection run sends one page per list, not a full export. Alerts are unpaginated.\n\n## Alert-event workaround\nKeep alert_events_page_size at 100 or below. Verified 2026-08-31: larger page_size values are capped to 100 internally but has_more is calculated using the requested size, causing premature termination.\n\n## Access and errors\nOrg-admin keys access all current organization members data; non-admin and personal keys remain limited to the creators data. Membership and role are rechecked on requests.\n401: invalid/revoked key or removed membership. 403: missing scope. 404: resource unavailable or not visible, or no overview. 429: rate limited; retry later.\n\nThis shareable file contains no API keys, organization/user IDs, customer records, or saved responses.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://www.app.chatae.ai/api/v1",
      "type": "string",
      "description": "Canonical API base URL; no trailing slash."
    },
    {
      "key": "api_key",
      "value": "",
      "type": "string",
      "description": "Prefer a private environment variable with this name. Never share an actual credential in this file."
    },
    {
      "key": "account_id",
      "value": "",
      "type": "string",
      "description": "Optional account ID for manual detail requests. Auto-selected only during a collection run."
    },
    {
      "key": "contact_id",
      "value": "",
      "type": "string",
      "description": "Optional contact ID for manual detail requests. Auto-selected only during a collection run."
    },
    {
      "key": "alert_id",
      "value": "",
      "type": "string",
      "description": "Optional alert ID for manual detail requests. Auto-selected only during a collection run."
    },
    {
      "key": "limit",
      "value": "500",
      "type": "string",
      "description": "Accounts/research page size, 1–500."
    },
    {
      "key": "accounts_cursor",
      "value": "",
      "type": "string",
      "description": "Copy accounts next_cursor here and enable starting_after."
    },
    {
      "key": "research_cursor",
      "value": "",
      "type": "string",
      "description": "Copy research next_cursor here and enable starting_after. Reset when switching accounts."
    },
    {
      "key": "contacts_page",
      "value": "1",
      "type": "string",
      "description": "Set to contacts next_cursor to request the next page."
    },
    {
      "key": "contacts_page_size",
      "value": "500",
      "type": "string",
      "description": "Contact page size, 1–500."
    },
    {
      "key": "alert_events_page",
      "value": "1",
      "type": "string",
      "description": "Set to alert-events next_cursor to request the next page."
    },
    {
      "key": "alert_events_page_size",
      "value": "100",
      "type": "string",
      "description": "Keep at 100 or below until the alert-event pagination bug is fixed."
    }
  ],
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "pm.test(\"HTTP 200\", function () { pm.response.to.have.status(200); });",
          "pm.test(\"Response is valid JSON\", function () { pm.response.json(); });"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "Identity",
      "item": [
        {
          "name": "Get current key info",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "me"
              ]
            },
            "description": "Verify authentication, organization binding, admin status, and available read scopes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Identity includes key scopes and organization context\", function () {",
                  "    pm.expect(Array.isArray(body.key && body.key.scopes)).to.eql(true);",
                  "    pm.expect(body.organization_id === null || typeof body.organization_id === \"string\").to.eql(true);",
                  "    pm.expect(typeof body.organization_admin).to.eql(\"boolean\");",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Accounts",
      "item": [
        {
          "name": "List accounts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/accounts?limit={{limit}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "accounts"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "{{limit}}",
                  "description": "Page size, 1–500."
                },
                {
                  "key": "starting_after",
                  "value": "{{accounts_cursor}}",
                  "description": "Enable after setting accounts_cursor from next_cursor.",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Optional name/domain search.",
                  "disabled": true
                }
              ]
            },
            "description": "Requires accounts:read. Cursor pagination. Selects a sample account_id during a collection run."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Collection response has data and pagination fields\", function () {",
                  "    pm.expect(Array.isArray(body.data)).to.eql(true);",
                  "    pm.expect(typeof body.has_more).to.eql(\"boolean\");",
                  "    pm.expect(body.next_cursor === null || typeof body.next_cursor === \"string\").to.eql(true);",
                  "    if (body.has_more) pm.expect(Boolean(body.next_cursor)).to.eql(true);",
                  "  });",
                  "  if (Array.isArray(body.data) && body.data.length && !pm.variables.get(\"account_id\")) {",
                  "    pm.variables.set(\"account_id\", body.data[0].id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Get account",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/accounts/{{account_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "accounts",
                "{{account_id}}"
              ]
            },
            "description": "Requires accounts:read. Set account_id or run List accounts in the same collection run."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get(\"account_id\")) {",
                  "  console.warn(\"Skipped: set account_id, or run the matching list request first in the Collection Runner.\");",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"Resource has an ID\", function () {",
                  "    pm.expect(typeof pm.response.json().id).to.eql(\"string\");",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "List account research",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/accounts/{{account_id}}/research?limit={{limit}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "accounts",
                "{{account_id}}",
                "research"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "{{limit}}",
                  "description": "Page size, 1–500."
                },
                {
                  "key": "starting_after",
                  "value": "{{research_cursor}}",
                  "description": "Enable after setting research_cursor from next_cursor.",
                  "disabled": true
                },
                {
                  "key": "updated_after",
                  "value": "",
                  "description": "Optional ISO 8601 timestamp for incremental sync.",
                  "disabled": true
                }
              ]
            },
            "description": "Requires accounts:read. Cursor pagination, independent of the account-list cursor."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get(\"account_id\")) {",
                  "  console.warn(\"Skipped: set account_id, or run the matching list request first in the Collection Runner.\");",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Collection response has data and pagination fields\", function () {",
                  "    pm.expect(Array.isArray(body.data)).to.eql(true);",
                  "    pm.expect(typeof body.has_more).to.eql(\"boolean\");",
                  "    pm.expect(body.next_cursor === null || typeof body.next_cursor === \"string\").to.eql(true);",
                  "    if (body.has_more) pm.expect(Boolean(body.next_cursor)).to.eql(true);",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Get account overview",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/accounts/{{account_id}}/overview",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "accounts",
                "{{account_id}}",
                "overview"
              ]
            },
            "description": "Requires accounts:read. Returns 404 if no accessible overview has been generated; this is shown as a failed HTTP-200 check for investigation."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get(\"account_id\")) {",
                  "  console.warn(\"Skipped: set account_id, or run the matching list request first in the Collection Runner.\");",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"Resource has an ID\", function () {",
                  "    pm.expect(typeof pm.response.json().id).to.eql(\"string\");",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Contacts",
      "item": [
        {
          "name": "List contacts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts?page={{contacts_page}}&page_size={{contacts_page_size}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{contacts_page}}",
                  "description": "Starts at 1; next_cursor contains the next page number."
                },
                {
                  "key": "page_size",
                  "value": "{{contacts_page_size}}",
                  "description": "Page size, 1–500."
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Optional name/email/account search.",
                  "disabled": true
                },
                {
                  "key": "account_id",
                  "value": "{{account_id}}",
                  "description": "Optional account filter.",
                  "disabled": true
                },
                {
                  "key": "updated_after",
                  "value": "",
                  "description": "Optional ISO 8601 timestamp for incremental sync.",
                  "disabled": true
                }
              ]
            },
            "description": "Requires contacts:read. Page-based pagination. Selects a sample contact_id during a collection run."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Collection response has data and pagination fields\", function () {",
                  "    pm.expect(Array.isArray(body.data)).to.eql(true);",
                  "    pm.expect(typeof body.has_more).to.eql(\"boolean\");",
                  "    pm.expect(body.next_cursor === null || typeof body.next_cursor === \"string\").to.eql(true);",
                  "    if (body.has_more) pm.expect(Boolean(body.next_cursor)).to.eql(true);",
                  "  });",
                  "  if (Array.isArray(body.data) && body.data.length && !pm.variables.get(\"contact_id\")) {",
                  "    pm.variables.set(\"contact_id\", body.data[0].id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Get contact",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contacts/{{contact_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contacts",
                "{{contact_id}}"
              ]
            },
            "description": "Requires contacts:read. Set contact_id or run List contacts in the same collection run."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get(\"contact_id\")) {",
                  "  console.warn(\"Skipped: set contact_id, or run the matching list request first in the Collection Runner.\");",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"Resource has an ID\", function () {",
                  "    pm.expect(typeof pm.response.json().id).to.eql(\"string\");",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Alerts",
      "item": [
        {
          "name": "List alerts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts"
              ]
            },
            "description": "Requires alerts:read. Returns all accessible alerts without pagination. Selects a sample alert_id during a collection run."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Collection response has data and pagination fields\", function () {",
                  "    pm.expect(Array.isArray(body.data)).to.eql(true);",
                  "    pm.expect(typeof body.has_more).to.eql(\"boolean\");",
                  "    pm.expect(body.next_cursor === null || typeof body.next_cursor === \"string\").to.eql(true);",
                  "    if (body.has_more) pm.expect(Boolean(body.next_cursor)).to.eql(true);",
                  "  });",
                  "  if (Array.isArray(body.data) && body.data.length && !pm.variables.get(\"alert_id\")) {",
                  "    pm.variables.set(\"alert_id\", body.data[0].id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Get alert",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts/{{alert_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts",
                "{{alert_id}}"
              ]
            },
            "description": "Requires alerts:read. Includes notes. Set alert_id or run List alerts in the same collection run."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.variables.get(\"alert_id\")) {",
                  "  console.warn(\"Skipped: set alert_id, or run the matching list request first in the Collection Runner.\");",
                  "  pm.execution.skipRequest();",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"Resource has an ID\", function () {",
                  "    pm.expect(typeof pm.response.json().id).to.eql(\"string\");",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "List alert events",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alert-events?page={{alert_events_page}}&page_size={{alert_events_page_size}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alert-events"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "{{alert_events_page}}",
                  "description": "Starts at 1; next_cursor contains the next page number."
                },
                {
                  "key": "page_size",
                  "value": "{{alert_events_page_size}}",
                  "description": "Use 1–100 to avoid the confirmed pagination bug."
                },
                {
                  "key": "alert_id",
                  "value": "{{alert_id}}",
                  "description": "Optional alert filter.",
                  "disabled": true
                },
                {
                  "key": "account_id",
                  "value": "{{account_id}}",
                  "description": "Optional account filter.",
                  "disabled": true
                },
                {
                  "key": "contact_id",
                  "value": "{{contact_id}}",
                  "description": "Optional contact filter.",
                  "disabled": true
                },
                {
                  "key": "event_type",
                  "value": "",
                  "description": "Optional: event, completion, or error.",
                  "disabled": true
                },
                {
                  "key": "q",
                  "value": "",
                  "description": "Optional title/summary search.",
                  "disabled": true
                },
                {
                  "key": "updated_after",
                  "value": "",
                  "description": "Optional ISO 8601 timestamp.",
                  "disabled": true
                }
              ]
            },
            "description": "Requires alerts:read. IMPORTANT: keep page_size at 100 or below; larger values currently cause has_more to terminate too early. Copy next_cursor into alert_events_page to continue."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test(\"Collection response has data and pagination fields\", function () {",
                  "    pm.expect(Array.isArray(body.data)).to.eql(true);",
                  "    pm.expect(typeof body.has_more).to.eql(\"boolean\");",
                  "    pm.expect(body.next_cursor === null || typeof body.next_cursor === \"string\").to.eql(true);",
                  "    if (body.has_more) pm.expect(Boolean(body.next_cursor)).to.eql(true);",
                  "  });",
                  "}"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    }
  ]
}
