These rules apply to every request in this guide.
Base URL
The base URL is https://api.usehearsay.com. All paths in this guide are relative to it.
Headers on authenticated calls
| Header | Value |
|---|---|
Authorization |
Bearer <token> (see "Step 1") |
organization |
The id of the organization you are acting in |
The sign-in routes need no token: register, login, refresh, the email-availability check and the two password-reset routes. Every other route needs the Authorization header.
Almost every case, extraction-code, conversation and export route requires the organization header. If it is missing or wrong, the call fails even with a valid token. Each endpoint in the reference lists the header when it needs it.
Response envelope
Every success response is wrapped:
data: the actual result.statusCode: the HTTP status.timestamp: the time of the response.
Always read the result from data. Treat any 2xx status as success: most creates return 201, and so do some updates.
Lists and paging
List routes accept page and perPage (or skip and limit), plus sortBy and sortOrder. The default is page 1 with 10 rows per page.
A list response has one more level: data holds a page object, and the rows are in data.data. The page object also carries total, totalPages, nextPage and previousPage.
To read everything, keep requesting pages until nextPage is null. Do not assume one page holds every row. If the order matters, send sortBy and sortOrder yourself instead of relying on the defaults.
Errors
An error response has statusCode, timestamp, path and message. message can be a string, or an object with a machine-readable code. When a code is present, branch on the code, not on the message text.
Rate limits
The API allows about 200 requests per minute. Some auth routes have lower limits; for example, the email-availability check allows 10 per minute. On HTTP 429, wait and retry with backoff. Do not retry in a tight loop.
Ids
Every id (case, extraction code, export, device, message, tag, file) is a 24-character hex string. Store the ids the API returns; never build or guess them.
The one exception is the extraction code's code, the short value the custodian types in (for example K7Q2-9XPL). Routes that take {extractionCode} expect that code value, not an id.
Where to find each id:
| You need | Where it is |
|---|---|
| Organization id | data.userProfile.userOrganizations[].organization.id (login or register) |
| Case id | data.case.id (create case); data.data[].case.id (list cases) |
| Extraction code | data.code for the code value, data.id for its id (create code) |
| Device id | data.data[].dataSources[].sourceId on iOS and Android rows (data-status-v2) |
| Conversation id | data.data[].id (GET /conversation/search) |
| Message id | data.data[].id (GET /conversation/search-entries and the message searches) |
| Tag id | data.data[].id (GET /case/tags/{caseId}) |
| Export id | data.id (start export); data.data[].id (list exports) |
| File id | file.id on a completed export |
Dates
Send dates as ISO 8601, for example 2026-11-30T00:00:00.000Z.
An authenticated request:
curl "https://api.usehearsay.com/case?page=1&sortBy=updatedAt&sortOrder=desc" \
-H "Authorization: Bearer $TOKEN" \
-H "organization: $ORG_ID"
A single result:
{
"data": { "...": "the result" },
"statusCode": 200,
"timestamp": "2026-01-15T10:15:00.000Z"
}
A list result. The rows are in data.data:
{
"data": {
"data": [{ "...": "one row" }],
"page": 1,
"perPage": 10,
"total": 42,
"totalPages": 5,
"nextPage": 2,
"previousPage": null
},
"statusCode": 200,
"timestamp": "2026-01-15T10:15:00.000Z"
}