# Get one LinkedIn scraper (poll its progress)

Source: https://docs.emelia.io/docs/linkedin-scraper/get-scraper

Guide: [LinkedIn scraper](https://docs.emelia.io/docs/linkedin-scraper.md)

`GET https://api.emelia.io/linkedin-scrappers/{id}`

Returns one scraper with its progress. This is the call to poll while an extraction or an enrichment runs (every 30 to 60 seconds is enough).

**Reading the progress**
- `status`: see `ScrapperStatus`. The extraction is over when `status` is `done`; `error` gives the reason when `status` is `error`.
- `scrapIndex` contacts extracted out of `totalContact` (0 until LinkedIn reports the result count, then capped by the plan rules).
- Segmented searches: `segments`, `estimatedEnd`, `nextRunningSegment`, `betweenSegment`.
- After `POST /{id}/enrich`: `status` is `enriching`, then `done` again. `enrichIndex` and `enrichFound` are only filled at the end of the enrichment.
- `convertedListId`: id of the Emelia list automatically created from a finished scraper (usually within a few minutes after `done`), `null` before.

Unlike the list route, this response also contains `userId`.

## Authentication

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

## Path parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes | Scrapper id (`_id` returned by `POST /linkedin-scrappers` or `GET /linkedin-scrappers`). 24 hexadecimal characters. |

## Example request

**cURL**

```bash
curl -X GET "https://api.emelia.io/linkedin-scrappers/66f1a2b3c4d5e6f7a8b9c0d1" \
  -H "Authorization: $EMELIA_API_KEY"
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/linkedin-scrappers/66f1a2b3c4d5e6f7a8b9c0d1', {
  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/linkedin-scrappers/66f1a2b3c4d5e6f7a8b9c0d1",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
)
print(response.json())
```

## Responses

### 200: The scraper.

| Field | Type | Required | Description |
|---|---|---|---|
| `success` | boolean | yes |  |
| `scrap` | object | yes | A scraper (one extraction of a LinkedIn search). |
| `scrap._id` | string | no | Scrapper id. |
| `scrap.userId` | string | no | Workspace id. Only returned by `GET /linkedin-scrappers/{id}`. |
| `scrap.name` | string | no |  |
| `scrap.url` | string | no | Search URL given at creation. |
| `scrap.scrapType` | enum: `people`, `account`, `classic` | no | `people`: Sales Navigator people search or lead list. `account`: Sales Navigator account (company) search; detected when the extraction starts, so it reads `people` before. `classic`: classic LinkedIn people search. |
| `scrap.status` | enum: `running`, `planified`, `paused`, `enriching`, `done`, `error` | no | Life cycle of a scraper: - `planified`: waiting for `plannedStart`. - `running`: extraction in progress. For a segmented extraction, also between two chunks (see `betweenSegment`). - `paused`: paused by you (segmented extractions only) or by Emelia when credits ran out. Restart with resume. - `done`: extraction finished; also the state after an enrichment ends. - `enriching`: email enrichment in progress (`POST /{id}/enrich`). - `error`: stopped, the reason is in `error`. |
| `scrap.error` | enum: `INVALID_COOKIE`, `2FA_REQUIRED`, `BAD_SALES_NAV_URL`, `NEED_SUBSCRIPTION`, `TOO_MUCH_RESULT`, `RATE_LIMIT`, `SEGMENTATION_ERROR`, `PROCESS_ERROR`, `NO_AUTH_FOUND` or null | no | Error code when `status` is `error`. |
| `scrap.errorDetail` | object | no | Internal diagnostic written by the scraper (free text in French). Not a stable contract: use `error`. |
| `scrap.totalContact` | integer | no | Contacts to extract: 0 until LinkedIn reports the result count, then capped by the plan rules and `maxContacts`. Final count once `done`. Reduced by a split or a filter. |
| `scrap.scrapIndex` | integer | no | Contacts extracted so far. |
| `scrap.maxContacts` | integer | no | Value of `max_contacts` at creation, 0 = no limit. |
| `scrap.segmented` | boolean | no | True when the search was split into chunks (Grow and Scale, 3,000 results or more). |
| `scrap.segments` | array of object | no |  |
| `scrap.estimatedEnd` | string (date-time) | no | Estimated end of a segmented extraction. |
| `scrap.nextRunningSegment` | string or null (date-time) | no | Computed for running segmented scrapers: when the next chunk is scheduled. |
| `scrap.betweenSegment` | boolean | no | Computed for running segmented scrapers: true when no chunk is being extracted right now. |
| `scrap.plannedStart` | string (date-time) | no | Scheduled start, when given at creation. |
| `scrap.date` | string (date-time) | no | Creation date. |
| `scrap.lastUpdate` | string (date-time) | no | Last time the scraper wrote progress. |
| `scrap.enrichEnabled` | boolean | no | True once an email enrichment was started. |
| `scrap.startEnrichDate` | string (date-time) | no | Start of the email enrichment. |
| `scrap.enrichIndex` | integer | no | Contacts processed by the enrichment. Only set at the end. |
| `scrap.enrichFound` | integer | no | Emails found. Only set at the end of the enrichment. |
| `scrap.enrichPhoneEnabled` | boolean | no | True when a phone enrichment was started from the app. |
| `scrap.enrichPhoneIndex` | integer | no |  |
| `scrap.enrichPhoneFound` | integer | no |  |
| `scrap.processing` | boolean | no | True while `POST /{id}/filter` runs. |
| `scrap.webhooks` | array of object | no |  |
| `scrap.authes` | array of string | no | LinkedIn account ids used by the scraper. |
| `scrap.auth` | string | no | Legacy single account id (old scrapers only). |
| `scrap.convertedListId` | string or null | no | Id of the Emelia list created automatically from the finished scraper, `null` before. |

```json
{
  "success": true,
  "scrap": {
    "_id": "66f1a2b3c4d5e6f7a8b9c0d1",
    "userId": "65a0b1c2d3e4f5a6b7c8d9e0",
    "authes": [
      "66e0f1a2b3c4d5e6f7a8b9c0",
      "66e0f1a2b3c4d5e6f7a8b9c1"
    ],
    "name": "Acme CTOs France",
    "url": "https://www.linkedin.com/sales/search/people?query=(filters%3AList((type%3ACURRENT_TITLE%2Cvalues%3AList((text%3ACTO%2CselectionType%3AINCLUDED)))%2C(type%3AREGION%2Cvalues%3AList((text%3AFrance%2CselectionType%3AINCLUDED)))))",
    "scrapType": "people",
    "status": "running",
    "totalContact": 7400,
    "scrapIndex": 5000,
    "maxContacts": 0,
    "segmented": true,
    "segments": [
      {
        "_id": "66f1a2b3c4d5e6f7a8b9c101",
        "url": "https://www.linkedin.com/sales/search/people?query=(filters%3AList((type%3ACURRENT_TITLE%2Cvalues%3AList((text%3ACTO%2CselectionType%3AINCLUDED)))%2C(type%3AREGION%2Cvalues%3AList((text%3AFrance%2CselectionType%3AINCLUDED)))%2C(type%3ACOMPANY_HEADCOUNT%2Cvalues%3AList((id%3AB%2Ctext%3A1-10%2CselectionType%3AINCLUDED)))))",
        "scrapIndex": 2500,
        "count": 2500,
        "complete": true
      },
      {
        "_id": "66f1a2b3c4d5e6f7a8b9c102",
        "url": "https://www.linkedin.com/sales/search/people?query=(filters%3AList((type%3ACURRENT_TITLE%2Cvalues%3AList((text%3ACTO%2CselectionType%3AINCLUDED)))%2C(type%3AREGION%2Cvalues%3AList((text%3AFrance%2CselectionType%3AINCLUDED)))%2C(type%3ACOMPANY_HEADCOUNT%2Cvalues%3AList((id%3AC%2Ctext%3A11-50%2CselectionType%3AINCLUDED)))))",
        "scrapIndex": 2500,
        "count": 2500,
        "complete": true
      },
      {
        "_id": "66f1a2b3c4d5e6f7a8b9c103",
        "url": "https://www.linkedin.com/sales/search/people?query=(filters%3AList((type%3ACURRENT_TITLE%2Cvalues%3AList((text%3ACTO%2CselectionType%3AINCLUDED)))%2C(type%3AREGION%2Cvalues%3AList((text%3AFrance%2CselectionType%3AINCLUDED)))%2C(type%3ACOMPANY_HEADCOUNT%2Cvalues%3AList((id%3AD%2Ctext%3A51-200%2CselectionType%3AINCLUDED)))))",
        "scrapIndex": 0,
        "count": 2400
      }
    ],
    "estimatedEnd": "2026-10-12T13:40:00.000Z",
    "nextRunningSegment": "2026-10-12T09:11:00.000Z",
    "betweenSegment": true,
    "date": "2026-10-11T08:02:41.512Z",
    "lastUpdate": "2026-10-11T11:14:03.220Z",
    "enrichEnabled": false,
    "enrichIndex": 0,
    "enrichFound": 0,
    "enrichPhoneEnabled": false,
    "enrichPhoneIndex": 0,
    "enrichPhoneFound": 0,
    "webhooks": [
      {
        "_id": "66f1a2b3c4d5e6f7a8b9c0f1",
        "webhookUrl": "https://hooks.acme.example/emelia/scrapper",
        "events": [
          "FINISHED",
          "ERROR"
        ]
      }
    ],
    "convertedListId": null
  }
}
```

### 400: Unknown or deleted scraper.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

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

### 401: Missing or invalid API key (`Authorization` header).

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

```json
{
  "error": "Authentication required"
}
```

### 404: The id is not 24 hexadecimal characters: no route matches and the server answers with its default HTML page (not JSON).

Content type: `text/html`.

### 500: Unexpected server error.

Current behavior: also returned when the per-minute API quota of the workspace is exceeded (30 requests per minute without subscription, 100 on Start, 300 on Grow, 1,000 on Scale). A 429 would be expected; retry after a minute.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

```json
{
  "error": "Internal server error"
}
```
