# List or find the contacts of a list

Source: https://docs.emelia.io/docs/lists/list-or-find-contacts

Guide: [Lists & contacts](https://docs.emelia.io/docs/lists.md)

`GET https://api.emelia.io/lists/list/{listId}/rows`

Two modes.

**Exact lookup** (use it before an update or a delete): pass `email` OR `linkedinUrl`. The email is matched exactly but case-insensitively, the LinkedIn URL is compared as an exact string (a trailing slash or a missing `www` is a different URL). The search covers this list and the lists linked to it, returns up to 50 matches, ignores `search`, filters and pagination, and returns the full contact objects. The envelope then has `page: 1`, `pageSize` equal to the number of items (1 when empty) and no `customVariables`.

**Paginated listing**: without `email` and `linkedinUrl`, returns one page of the contacts stored in this list (not in linked lists), in creation order unless `sortBy` is set. Company rows without a contact (company-only lists) are included with `rowKind: "company_only"` and an id of the form `co:<companyId>` that the contact routes do not accept.

In paginated mode, when the list has column settings (every list created by this API or by the current app has them), each item only contains the fields of the columns visible in the list table of the app: a column hidden in the app disappears from the API response, `labels` is not returned and `company` carries no `companyId`. Use the exact lookup to get the full object.

Deep pages are expensive (the service sorts `page x pageSize` rows to serve one page): page through large lists with `pageSize=100` and avoid restarting from page 1, or use the export.

## Authentication

Send your API key in the `Authorization` header: `Authorization: <your API key>`.

## Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `listId` | string | yes | List id. |

## Query parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `email` | string (email) | no | Exact lookup by email (case-insensitive). Mutually exclusive with `linkedinUrl`. |
| `linkedinUrl` | string | no | Exact lookup by LinkedIn profile URL (compared verbatim with the stored `linkedinUrlProfile`). Mutually exclusive with `email`. |
| `page` | integer | no | Page number. Invalid or negative values fall back to 1. Default: `1`. Range: 1 to .... |
| `pageSize` | integer | no | Page size, clamped between 1 and 100. Default: `50`. Range: 1 to 100. |
| `search` | string | no | Free-text search, 200 characters max. A full email address is matched exactly (case-insensitive). Otherwise every word must appear (case-insensitive substring) in one of `firstName`, `lastName`, `fullName`, `email` or `linkedinUrlProfile`; company-only rows are matched on the company name. |
| `sortBy` | enum: `firstName`, `lastName`, `fullName`, `jobTitle`, `email`, `phone`, `linkedinUrlProfile`, `country`, `city`, `createdAt`, `updatedAt` | no | Sort field. Text fields sort case- and accent-insensitively. Any other value is ignored (creation order). |
| `sortDir` | enum: `asc`, `desc` | no | Sort direction, used with `sortBy`. Default: `"asc"`. |
| `statuses` | string | no | Comma-separated engagement statuses (as stored on the contacts, for example `SENT`, `OPENED`, `MAIL_REPLIED`, `ACCEPTED`). The token `NO_ACTIVITY` matches contacts without status. Up to 30 values. Company-only rows are excluded when this filter is set. |
| `filters` | string | no | URL-encoded JSON array of column rules (up to 10), all combined with AND. See `ListsFilterRule`. |

## Example request

**cURL**

```bash
curl -X GET "https://api.emelia.io/lists/list/65f1a2b3c4d5e6f760f68df4/rows" \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/lists/list/65f1a2b3c4d5e6f760f68df4/rows', {
  method: 'GET',
  headers: { Authorization: process.env.EMELIA_API_KEY },
});
console.log(await response.json());
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.emelia.io/lists/list/65f1a2b3c4d5e6f760f68df4/rows",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
)
print(response.json())
```

## Responses

### 200: Contacts of the list.

| Field | Type | Required | Description |
|---|---|---|---|
| `items` | array of object | yes |  |
| `items[].id` | string | yes | Contact id, or `co:<companyId>` for a company-only row. |
| `items[].rowKind` | enum: `lead`, `company_only` | yes |  |
| `items[].firstName` | string | no |  |
| `items[].lastName` | string | no |  |
| `items[].fullName` | string | no | For a company-only row: the company name, or `-`. |
| `items[].jobTitle` | string | no |  |
| `items[].gender` | string or null | no | Free text as imported. |
| `items[].email` | string or null | no |  |
| `items[].secondaryEmail` | string or null | no |  |
| `items[].phone` | string or null | no |  |
| `items[].mobilePhone` | string or null | no |  |
| `items[].linkedinUrlProfile` | string or null | no |  |
| `items[].twitterUrl` | string or null | no |  |
| `items[].country` | string | no |  |
| `items[].city` | string | no |  |
| `items[].seniority` | string | no | Known levels are returned with spaces (for example `C LEVEL`). |
| `items[].department` | string | no |  |
| `items[].bio` | string | no |  |
| `items[].profilePictureUrl` | string or null | no |  |
| `items[].emailVerified` | enum: `verified`, `unverified`, `invalid`, `unknown` | no | Result of the last email verification; `unknown` when never verified. |
| `items[].status` | string | no | Latest engagement status written by the campaigns (for example `SENT`, `OPENED`, `MAIL_REPLIED`, `ACCEPTED`), empty when none. |
| `items[].company` | object or null | no |  |
| `items[].labels` | array of object | no | Always empty. Not returned in the paginated listing. |
| `items[].customFields` | array of object | no |  |
| `items[].createdAt` | string | no | ISO date, or empty string. |
| `items[].updatedAt` | string | no | ISO date, or empty string. |
| `total` | integer | yes | Total matching rows (paginated mode) or number of matches (exact lookup). |
| `page` | integer | yes |  |
| `pageSize` | integer | yes |  |
| `customVariables` | array of object | no | Custom variables shown in this list or used by the returned rows. Absent in the exact lookup mode. |
| `customVariables[].technicalName` | string | yes |  |
| `customVariables[].displayName` | string | yes |  |
| `customVariables[].schema` | enum: `lead`, `company` | yes |  |

```json
{
  "items": [
    {
      "id": "string",
      "rowKind": "lead",
      "firstName": "string",
      "lastName": "string",
      "fullName": "string",
      "jobTitle": "string",
      "gender": "string",
      "email": "string",
      "secondaryEmail": "string",
      "phone": "string",
      "mobilePhone": "string",
      "linkedinUrlProfile": "string",
      "twitterUrl": "string",
      "country": "string",
      "city": "string",
      "seniority": "string",
      "department": "string",
      "bio": "string",
      "profilePictureUrl": "string",
      "emailVerified": "verified",
      "status": "string",
      "company": {},
      "labels": [],
      "customFields": [],
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "total": 0,
  "page": 0,
  "pageSize": 0,
  "customVariables": [
    {
      "technicalName": "string",
      "displayName": "string",
      "schema": "lead"
    }
  ]
}
```

### 401: Missing, unknown or disabled API key, or the account of the key no longer exists. Note the body shape: `error` and `code`, not `message`.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string | yes |  |
| `code` | enum: `AUTH_REQUIRED`, `ACCOUNT_NOT_FOUND` | yes |  |

```json
{
  "error": "string",
  "code": "AUTH_REQUIRED"
}
```

### 404: The list does not exist or belongs to another account.

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes | English, human-readable message. |
| `code` | string | no | Stable error code, when available (for example `PAYLOAD_TOO_LARGE`, `INVALID_JSON`, `export_too_large`, `companies_only`, `enrichment_running`). |
| `params` | object | no |  |
| `error` | string | no | Raw internal message (unexpected errors only). |

```json
{
  "message": "List not found"
}
```

### 422: `email` and `linkedinUrl` sent together, or invalid `filters`.

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes | English, human-readable message. |
| `code` | string | no | Stable error code, when available (for example `PAYLOAD_TOO_LARGE`, `INVALID_JSON`, `export_too_large`, `companies_only`, `enrichment_running`). |
| `params` | object | no |  |
| `error` | string | no | Raw internal message (unexpected errors only). |

```json
{
  "message": "string",
  "code": "string",
  "params": {},
  "error": "string"
}
```

### 500: Unexpected server error. `error`, when present, is a raw internal message: do not parse it. Retry later.

| Field | Type | Required | Description |
|---|---|---|---|
| `message` | string | yes | English, human-readable message. |
| `code` | string | no | Stable error code, when available (for example `PAYLOAD_TOO_LARGE`, `INVALID_JSON`, `export_too_large`, `companies_only`, `enrichment_running`). |
| `params` | object | no |  |
| `error` | string | no | Raw internal message (unexpected errors only). |

```json
{
  "message": "Hmm... Something went wrong. Please try again later."
}
```
