API Documentation
Roster ¶
Bulk sync of an external org chart — roles, brands (offices), and users — into Rechat under a single brand. Intended for identity/HR integrations that own the source of truth and push their full roster on a schedule.
Everything hangs off a roster source, one per brand. Create it once, then
PUT the roster to it. Records are keyed by the caller’s own external_id, so
syncing is idempotent: an external_id seen before updates the existing record,
a new one creates it, and re-sending the same payload is a no-op.
PUT treats the payload as the complete roster: a record the source has sent
before but omits now is marked deleted. PATCH treats it as a fragment to merge,
so absence never removes anything — use it when the caller only knows about the
records it is sending. To remove records under PATCH, delete them explicitly.
A sync payload has three optional arrays, each resolved by external_id:
-
roles—{ external_id, name, acl }, whereaclis an array of Rechat permission strings (e.g.CRM,Deals,Admin). -
brands—{ external_id, name, type, parent }.typeisBrokerageorOffice;parentreferences another brand’sexternal_idto build the tree. -
users—{ external_id, email, first_name, last_name, user_type, memberships }.membershipsis an array of{ brand, role }pairs referencing brand and roleexternal_ids, placing the user in the tree. Missing brand/role references in a membership are skipped rather than erroring.
Omitting an array leaves that dimension untouched. Related records are returned
via associations — request them with ?associations[]=roster_source.roles and
?associations[]=roster_source.users.
Endpoints
Create a roster source ¶
Create a roster sourcePOST/brands/:brand/roster
Example URI
- brand
string(required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
200Body
{
"code": "OK",
"data": {
"type": "roster_source",
"id": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
}
}Get a brand's roster source ¶
Get a brand's roster sourceGET/brands/:brand/roster
Example URI
- brand
string(required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
200Body
{
"code": "OK",
"data": {
"type": "roster_source",
"id": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
}
}Sync roster data ¶
Sync roster dataPUT/brands/:brand/roster
Accepts any combination of roles, brands, and users. The example below
syncs all three at once; sending a single array performs a partial sync of just
that dimension. Records the source previously sent but omits here are marked
deleted, including memberships.
Example URI
- brand
string(required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1- associations
string(required) Example: roster_source.roles,roster_source.users
Body
{
"roles": [
{
"external_id": "agent",
"name": "Agent",
"acl": [
"CRM",
"Deals"
]
},
{
"external_id": "admin",
"name": "Admin",
"acl": [
"Admin",
"BackOffice"
]
}
],
"brands": [
{
"external_id": "region-east",
"name": "East Region",
"type": "Brokerage"
},
{
"external_id": "region-west",
"name": "West Region",
"type": "Brokerage"
},
{
"external_id": "office-ny",
"name": "New York Office",
"type": "Office",
"parent": "region-east"
},
{
"external_id": "office-la",
"name": "Los Angeles Office",
"type": "Office",
"parent": "region-west"
}
],
"users": [
{
"external_id": "user-001",
"email": "[email protected]",
"first_name": "John",
"last_name": "Doe",
"user_type": "Agent",
"memberships": [
{
"brand": "office-ny",
"role": "agent"
}
]
},
{
"external_id": "user-002",
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Smith",
"user_type": "Agent",
"memberships": [
{
"brand": "office-la",
"role": "agent"
}
]
},
{
"external_id": "user-003",
"email": "[email protected]",
"first_name": "Admin",
"last_name": "User",
"user_type": "Agent",
"memberships": [
{
"brand": "region-east",
"role": "admin"
},
{
"brand": "region-west",
"role": "admin"
}
]
}
]
}200Body
{
"code": "OK",
"data": {
"type": "roster_source",
"id": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null,
"roles": [
{
"type": "roster_role",
"id": "854033c6-95a0-4973-bff6-02c6e7d4c0ba",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "agent",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
},
{
"type": "roster_role",
"id": "382fffa7-90a7-42c0-a80d-f804d8345619",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "admin",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
}
],
"users": [
{
"type": "roster_user",
"id": "f04d82e7-d28b-42af-84c1-4b01754ce4b9",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "user-001",
"object": {
"email": "[email protected]",
"last_name": "Doe",
"user_type": "Agent",
"first_name": "John",
"external_id": "user-001"
},
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
},
{
"type": "roster_user",
"id": "c4468c57-617e-4bf0-9739-a0299b38db02",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "user-002",
"object": {
"email": "[email protected]",
"last_name": "Smith",
"user_type": "Agent",
"first_name": "Jane",
"external_id": "user-002"
},
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
},
{
"type": "roster_user",
"id": "1748a9c5-dae5-4c07-a5e0-b2f21b2afe00",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "user-003",
"object": {
"email": "[email protected]",
"last_name": "User",
"user_type": "Agent",
"first_name": "Admin",
"external_id": "user-003"
},
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
}
]
}
}Merge roster data ¶
Merge roster dataPATCH/brands/:brand/roster
Same payload as PUT, but only adds and updates — nothing is removed for being
absent. The example sends one user; the users omitted from it stay untouched.
Example URI
- brand
string(required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1- associations
string(required) Example: roster_source.users
Body
{
"users": [
{
"external_id": "user-004",
"email": "[email protected]",
"first_name": "Merged",
"last_name": "Newcomer",
"user_type": "Agent",
"memberships": [
{
"brand": "office-ny",
"role": "agent"
}
]
}
]
}200Body
{
"code": "OK",
"data": {
"type": "roster_source",
"id": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null,
"users": [
{
"type": "roster_user",
"id": "f04d82e7-d28b-42af-84c1-4b01754ce4b9",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "user-001",
"object": {
"email": "[email protected]",
"last_name": "Doe Updated",
"user_type": "Agent",
"first_name": "John",
"external_id": "user-001"
},
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
},
{
"type": "roster_user",
"id": "a2255c4f-73e0-4da7-9b0a-98fb45816c38",
"source": "ed165540-51f8-44a2-9da4-c14352cf59cf",
"external_id": "user-004",
"object": {
"email": "[email protected]",
"last_name": "Newcomer",
"user_type": "Agent",
"first_name": "Merged",
"external_id": "user-004"
},
"created_at": 1793438883.978007,
"updated_at": 1793438883.978007,
"deleted_at": null
}
]
}
}
Each user may include `headshot_url`, a publicly reachable HTTPS image URL.
When roster sync creates a new Rechat account, the server downloads and
validates the image, stores it as a public User attachment and sets the
account's profile image through the regular avatar model. Existing accounts,
including accounts matched by email, keep their current photo. Preview does
not download or upload anything. URL-only changes do not update accounts.
JPEG, PNG, WebP and GIF input is accepted (10 MB, 40 million pixels maximum);
output is JPEG, at most 2048 pixels per side. Downloads have a 20-second
deadline and three-redirect limit. Private network destinations and URL
credentials are rejected. An import failure is logged by user/external ID
and does not prevent account creation. Later roster runs do not retry or
backfill headshots. Large initial batches should account for image download
time inside the request.