Back to top

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 }, where acl is an array of Rechat permission strings (e.g. CRM, Deals, Admin).

  • brands — { external_id, name, type, parent }. type is Brokerage or Office; parent references another brand’s external_id to build the tree.

  • users — { external_id, email, first_name, last_name, user_type, memberships }. memberships is an array of { brand, role } pairs referencing brand and role external_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 source
POST/brands/:brand/roster

Example URI

POST /brands/:brand/roster
URI Parameters
HideShow
brand
string (required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
Response  200
HideShow
Body
{
  "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 source
GET/brands/:brand/roster

Example URI

GET /brands/:brand/roster
URI Parameters
HideShow
brand
string (required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
Response  200
HideShow
Body
{
  "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 data
PUT/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

PUT /brands/:brand/roster
URI Parameters
HideShow
brand
string (required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
associations
string (required) Example: roster_source.roles,roster_source.users
Request
HideShow
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"
        }
      ]
    }
  ]
}
Response  200
HideShow
Body
{
  "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 data
PATCH/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

PATCH /brands/:brand/roster
URI Parameters
HideShow
brand
string (required) Example: 9a415605-84b6-4602-ab1e-bd9fe32fe9b1
associations
string (required) Example: roster_source.users
Request
HideShow
Body
{
  "users": [
    {
      "external_id": "user-004",
      "email": "[email protected]",
      "first_name": "Merged",
      "last_name": "Newcomer",
      "user_type": "Agent",
      "memberships": [
        {
          "brand": "office-ny",
          "role": "agent"
        }
      ]
    }
  ]
}
Response  200
HideShow
Body
{
          "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.

Generated by aglio on 31 Oct 2026