> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clustr-ai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List Owners

> List the people an opportunity can be assigned to

List the owner directory for your tenant. An owner is anyone accountable for an opportunity, whether or not they have a Clustr login: a teammate, an investor, an advisor, a friend. Use the returned `id` as `owner_id`, or the `email` as `owner_email`, with the [Update Opportunity](/api-reference/update-opportunity) endpoint.

## Query Parameters

<ParamField query="search" type="string">
  Filter owners by name or email. Omit to return the whole directory.
</ParamField>

<ParamField query="with_counts" type="string">
  Set to `true` to populate `opportunity_count` on each owner. Off by default, because counting costs a lookup per owner.
</ParamField>

<ParamField query="email" type="string">
  Restrict the directory to these exact addresses, matched case-insensitively. Repeat the parameter (`?email=a@x.com&email=b@x.com`) or comma-separate them (`?email=a@x.com,b@x.com`).

  Use this to resolve a batch of addresses in one request rather than one request each. An address that matches no owner is simply absent from the response, so comparing what you sent against what came back tells you which people are owners.
</ParamField>

<ParamField query="with_connector_counts" type="string">
  Set to `true` to populate `connector_count` and `connectors_by_tag` on each owner. Off by default, and separate from `with_counts`, because it costs an extra join.
</ParamField>

## Response Fields

<ResponseField name="owners" type="array">
  Array of owner objects.
</ResponseField>

<ResponseField name="owners[].id" type="string">
  Owner UUID. Pass this as `owner_id` when updating an opportunity.
</ResponseField>

<ResponseField name="owners[].email" type="string">
  Email address. Unique within your tenant.
</ResponseField>

<ResponseField name="owners[].first_name" type="string">
  First name. May be empty when the owner was created from an email alone.
</ResponseField>

<ResponseField name="owners[].last_name" type="string">
  Last name. May be empty when the owner was created from an email alone.
</ResponseField>

<ResponseField name="owners[].avatar_url" type="string">
  Profile picture URL. Empty when the owner has no picture.
</ResponseField>

<ResponseField name="owners[].is_member" type="boolean">
  `true` when the owner is also a member of your workspace with a login. `false` for directory-only owners, such as an investor or an advisor.
</ResponseField>

<ResponseField name="owners[].connector_count" type="integer">
  How many connectors this owner is accountable for. Present only when the request sets `with_connector_counts=true`.
</ResponseField>

<ResponseField name="owners[].connectors_by_tag" type="object">
  That owner's connectors broken down by the **connectors'** tags. Read it as "of the people
  this owner is accountable for, N are tagged X". The tags describe the connectors, not the owner.

  Present only when the request sets `with_connector_counts=true`. An owner with no connectors
  returns `{}`. A tag with no connectors is omitted rather than reported as `0`.

  A connector carrying several tags is counted under each, so the values can sum to **more** than
  `connector_count`. An untagged connector is in `connector_count` and in no bucket, so they can
  also sum to **less**. Do not derive one tag's count by subtracting another from the total.
</ResponseField>

<ResponseField name="owners[].opportunity_count" type="integer">
  How many opportunities are **assigned** to this owner. Returned as `0` unless the request sets `with_counts=true`.

  This is the raw assignment count, so it is usually higher than the `total` you get
  from [Search Opportunities](/api-reference/search-opportunities) filtered by the same
  owner. The search hides opportunities that are unengaged, terminal-status, still
  unscored, or whose prospect is one of your own connectors; this count includes all of
  them. Both numbers are correct, they answer different questions: this one is "what is
  this person accountable for", the search total is "what can they act on today".

  An owner with `opportunity_count` of `0` has nothing assigned at all, so they are also
  absent from the search. The reverse does not hold: a non-zero count can still return no
  search results.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://api.clustr-ai.com/api/public/owners?search=alice&with_counts=true" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "data": {
      "owners": [
        {
          "id": "3f2b1c40-8a11-4f2e-9d61-0f5a8c7a2b13",
          "email": "alice@yourco.com",
          "first_name": "Alice",
          "last_name": "Martin",
          "avatar_url": "https://cdn.clustr-ai.com/avatars/alice.png",
          "is_member": true,
          "opportunity_count": 12,
          "connector_count": 45,
          "connectors_by_tag": { "Employee": 1, "Friend": 44 }
        },
        {
          "id": "9c8e77d1-4b2a-4c8f-9a30-6e21d4f0aa57",
          "email": "sam@northstar.vc",
          "first_name": "Sam",
          "last_name": "Okafor",
          "avatar_url": "",
          "is_member": false,
          "opportunity_count": 3
        }
      ]
    }
  }
  ```

  ```json 401 theme={null}
  {
    "success": false,
    "error": { "message": "Invalid or missing API key", "code": "UNAUTHORIZED" }
  }
  ```
</ResponseExample>

## Finding teammates who have not built their own network

`connectors_by_tag` is how you tell a teammate who was added by a bulk roster import
from one who has connected their own network. Import-created connectors carry whatever
tag the import assigned them; self-service connectors carry the tag your workspace uses
for personal contacts.

```
GET /owners?with_connector_counts=true&email=titouan@example.com,tony@example.com
```

```json theme={null}
{ "email": "titouan@example.com", "connector_count": 1,
  "connectors_by_tag": { "Employee": 1 } }
```

That owner is accountable for one connector, and it came from the roster import, so they
have not added anyone themselves.

Note that `opportunity_count` will **not** tell you this. Someone imported from a roster
has a non-zero opportunity count and looks active, even though they have contributed
nothing personally.

Which tag means "personal" is your decision, not ours -- tag names are yours to define,
so the API returns the breakdown rather than guessing.

### Checking a batch of people at once

Pass every address you care about in a single request — for example the members of
one deal channel — and read the counts back together:

```
GET /owners?with_connector_counts=true&email=a@acme.com,b@acme.com,c@acme.com
```

Owners that exist come back with their counts. Addresses that match no owner are
absent, so the set you sent minus the set you received is your list of people who are
not owners yet. There is no separate existence flag because absence already says it.
