Companies
Overview
A company groups the contacts that belong to the same customer. Each company belongs to a workspace and has a name, an optional domain, a phone number, and free-text notes.
When a contact is created and the host of its email address matches a company's domain, the contact is attached to that company automatically. That rule runs once, at the moment the contact is created, and never runs again. Changing a company's domain later does not move contacts that already exist, and a contact you move by hand stays where you put it.
Every company is scoped to the channels you can reach. A token that is limited to a subset of channels only sees companies with at least one ticket in those channels, and the contact_count and ticket_count on every response are narrowed the same way.
List Companies
Retrieve a paginated list of companies in the current workspace.
GET /api/companies
Query Parameters
| Parameter | Type | Description |
|---|---|---|
per_page |
integer | Results per page (default: 25, max: 100) |
sort |
string | Sort field: name, created_at, contact_count, ticket_count. Prefix with - for descending (default: name) |
filter[name] |
string | Partial match on company name |
filter[domain] |
string | Partial match on company domain |
filter[search] |
string | Search across both name and domain |
Example Request
curl "https://there-there.app/api/companies?filter[search]=acme&sort=-ticket_count&per_page=10" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Example Response
{
"data": [
{
"id": 1,
"ulid": "01kzx9m6kce637yvd1m95077kr",
"name": "Acme",
"domain": "acme.com",
"phone": "+32 3 555 55 55",
"notes": "Pays yearly.",
"contact_count": 4,
"ticket_count": 12
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "last_page": 2, "per_page": 10, "total": 14 }
}
Get Company
GET /api/companies/{company}
The {company} segment is the company ULID.
Example Request
curl https://there-there.app/api/companies/01kzx9m6kce637yvd1m95077ks \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Example Response
{
"data": {
"id": 2,
"ulid": "01kzx9m6kce637yvd1m95077ks",
"name": "Globex",
"domain": null,
"phone": null,
"notes": null,
"contact_count": 0,
"ticket_count": 0
}
}
A company in another workspace returns 404. A company in your own workspace returns 403 when none of its tickets sit in a channel your token can reach.
Create Company
POST /api/companies
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Company name |
domain |
string | No | Bare hostname, unique per workspace |
phone |
string | No | |
notes |
string | No | Free-text notes |
contact_ulids |
array | No | Contact ULIDs to attach to the new company |
The domain is trimmed and lowercased, and it has to be a bare hostname. An address such as jane@acme.com, a scheme such as https://acme.com, or anything with a path is rejected with 422. Two companies in the same workspace cannot claim the same domain, though two workspaces can each have their own company for acme.com.
Matching uses the full host of a contact's email address, not a shortened version of it, so a company that owns acme.com does not pick up contacts at mail.acme.com.
A ULID in contact_ulids that belongs to another workspace is ignored rather than rejected, so the request still returns 201 and simply attaches fewer contacts.
Example Request
curl -X POST https://there-there.app/api/companies \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme",
"domain": "acme.com",
"phone": "+32 3 555 55 55",
"notes": "Pays yearly.",
"contact_ulids": ["01hx9f3k2n7q4v8w0y2a4c6e8g"]
}'
Example Response
{
"data": {
"id": 3,
"ulid": "01kzx9m6kce637yvd1m95077kt",
"name": "Acme",
"domain": "acme.com",
"phone": "+32 3 555 55 55",
"notes": "Pays yearly.",
"contact_count": 1,
"ticket_count": 0
}
}
Returns 201 on success.
Update Company
PUT /api/companies/{company}
PATCH /api/companies/{company}
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | Cannot be empty when it is sent |
domain |
string | No | Bare hostname, unique per workspace |
phone |
string | No | |
notes |
string | No | Free-text notes |
All fields are optional. Omit a field to leave it unchanged. Send null for domain, phone, or notes to clear it. A name that is sent has to hold a value.
Changing the domain does not move any contact that already exists. It only decides which contacts created from now on are attached.
This endpoint does not accept contact_ulids. To move one contact into or out of a company, send company_ulid to Update Contact.
Example Request
curl -X PATCH https://there-there.app/api/companies/01kzx9m6kce637yvd1m95077kt \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corporation", "notes": "Renewal in March."}'
Example Response
{
"data": {
"id": 3,
"ulid": "01kzx9m6kce637yvd1m95077kt",
"name": "Acme Corporation",
"domain": "acme.com",
"phone": "+32 3 555 55 55",
"notes": "Renewal in March.",
"contact_count": 1,
"ticket_count": 0
}
}
Delete Company
DELETE /api/companies/{company}
Deleting a company keeps its contacts and every ticket they ever opened. Only the grouping goes: the contacts are detached and stay in the workspace.
Example Request
curl -X DELETE https://there-there.app/api/companies/01kzx9m6kce637yvd1m95077kt \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Returns 204 with an empty body on success.