# Workspaces and members

Which workspaces this key reaches, and who can work them.

Source: https://docs.leadarc.io/reference-workspaces.html

- [GET /v1/workspaces](#get-v1-workspaces)
- [GET /v1/workspaces/{ws}](#get-v1-workspaces-ws)
- [GET /v1/workspaces/{ws}/members](#get-v1-workspaces-ws-members)

## GET /v1/workspaces

`workspaces:read` · read bucket · returns list of workspace

List the workspaces this key reaches

```bash
curl -sS https://api.leadarc.io/v1/workspaces \
  -H "Authorization: Bearer $LEADARC_API_KEY"
```

```json
{
  "object": "list",
  "data": [
    {
      "object": "workspace",
      "id": "airpay",
      "name": "AirPay",
      "domain": "airpay.com",
      "logo_url": "https://…/logo.png",
      "active": true,
      "features": { "inbox": true, "scheduling": true, "events": true, "reports": true },
      "granted_scopes": ["leads:read", "leads:write", "threads:read", "tasks:read", "tasks:write"],
      "synced_at": "2026-07-27T06:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```
`id` is the slug. `features` is built from `workspaceFeaturesOf` over `clients.feature_inbox`, `feature_scheduling`, `feature_reports`. **`crm` and `calling` are omitted from the map entirely**, not returned as `false`, because both surfaces were removed from the product and `HIDDEN_FEATURE_KEYS = ["crm","calling"]` in the app. `events` is included because it is a real feature key that gates a whole endpoint family, and it rides on `feature_scheduling`.

There is no `created_at`. `clients.synced_at` exists but it is a sync timestamp written by the `sync-eb-workspaces` cron, so publishing it as `created_at` would be a field whose meaning was invented and whose value moves every six hours. It is surfaced under its real name, `synced_at`, with a note that it is when we last reconciled the workspace against EmailBison.

## GET /v1/workspaces/{ws}

`workspaces:read` · read bucket · returns workspace

One workspace

```bash
curl -sS https://api.leadarc.io/v1/workspaces/airpay?limit=25 \
  -H "Authorization: Bearer $LEADARC_API_KEY"
```

**Query parameters.** Anything else is [`400 unsupported_filter`](errors.html#status-400).

`include`

**Errors from this endpoint**, on top of the [common ones](errors.html): [`invalid_request`](errors.html#status-400), `unsupported_filter`.

- `?include=counts` adds `{ "counts": { "leads": 5152, "open_tasks": 41, "meetings_upcoming": 6 } }`. Bounded aggregate, on the `upstream` bucket even though it is local, because the lead count is a scan.
- `?include=sdr_notes` adds `{ "sdr_notes": "<p>…</p>" }` from `clients.sdr_notes`. Off by default. This is the internal playbook, and it should be an explicit ask.

**Important limit on `sdr_notes`:** `clients.sdr_notes` is the workspace-wide fallback only. The real playbook is per-ICP. `resolveSdrNotesForLead` in the app matches `sdr_note_sets.tag_ids` against a lead's source-campaign EmailBison tags using array overlap, and resolving those tags needs a live EmailBison call (`getReplySourceCampaign`). There is no workspace-level matcher. So the workspace-level field returns the fallback text, and the lead-level resolved notes are available on `GET.../leads/{lead}?include=sdr_notes`, which is documented as an `upstream`-bucket read for that reason.

## GET /v1/workspaces/{ws}/members

`members:read` · read bucket · returns list of member

People who can work this workspace

```bash
curl -sS https://api.leadarc.io/v1/workspaces/airpay/members?limit=25 \
  -H "Authorization: Bearer $LEADARC_API_KEY"
```

**Query parameters.** Anything else is `400 unsupported_filter`.

`active` `assignable` `cursor` `limit`

**Errors from this endpoint**, on top of the [common ones](errors.html): `invalid_request`, `unsupported_filter`.

```json
{
  "object": "list",
  "data": [{
    "object": "member",
    "id": "usr_zaDRRpdu8EPZSU3i89lvZgKPkVQo4uBFA_KOhm",
    "name": "Can Ersöz",
    "email": "can@leadarc.io",
    "avatar_url": "https://lh3.googleusercontent.com/…",
    "role": "admin",
    "timezone": "Europe/Istanbul",
    "active": true,
    "assignable": true,
    "created_at": "2026-04-02T11:20:00Z"
  }],
  "has_more": false,
  "next_cursor": null
}
```
**Notes.**

First, `users` is a global table with no `client_id`. The only membership table is `rep_client_assignments`, and **an admin has no rows in it** (the comment in the app says so directly: an admin sees every workspace by role). So a strict membership query silently omits every admin, which would leave `assignee_id`, `host_id`, and `booked_by_id` values returned elsewhere pointing at members not in this list. The API therefore returns the union of assigned members plus active admins, and marks admins with `assignable: true` only when the workspace's assignment config allows it.

Second, this list exposes agency staff emails to a client-scoped key. That is a deliberate call, because assignment and task endpoints are unusable without it, and because [`members:read`](scopes.html#all) is a separate scope that a read-only reporting key does not need.

`email` is `users.google_email`. `active` is `users.deactivated_at IS NULL`. `role` is `normalizeRole(users.role)`, which only ever returns `admin` or `user`. Seven legacy role values still exist in the column (`internal_sdr`, `client_sdr`, `client_viewer`, `client_ae`, `sdr`, `client`, `client_sdr_airpay`) and all fold to `user`.

---

---

Base URL `https://api.leadarc.io/v1`. Generated from the API source.
