# Connect a LinkedIn account for scraping (session cookies)

Source: https://docs.emelia.io/docs/linkedin-scraper/connect-account

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

`POST https://api.emelia.io/linkedin-scrappers/authes`

Connects a LinkedIn account for Sales Navigator scraping by sending the cookies of a browser session where the user is logged in to LinkedIn and Sales Navigator. No password is involved. The Emelia browser extension does this for you; use this route to automate it.

`li_at` is required. Strongly recommended: `li_a` (Sales Navigator session), `ua` (user agent of the browser the cookies come from), `additionnalsCookie` (the other linkedin.com cookies, as objects `{ name, value, domain }`), `viewport` (that browser window size) and `country` (ISO alpha-2 code: Emelia browses LinkedIn through a proxy located in this country; default the workspace country, then FR).

Returns `authId`, to use in `authes` of `POST /linkedin-scrappers`. The same `li_at` cannot be registered twice. When a session expires, update the existing account with `PUT /linkedin-scrappers/authes/{id}` instead of creating a new one.

Emelia stores these cookies and refreshes them after each scraping session. There is currently no API route to remove a connected scraping account.

Pitfalls: `jsessionid` is accepted but ignored (send `JSESSIONID` inside `additionnalsCookie`). Cookies sent as `"name=value"` strings are accepted but not used by the Sales Navigator scraper: send objects. Fields not described here are reserved for Emelia apps; any other field is rejected.

## Authentication

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

## Request body

Content type: `application/json`, required.

Session cookies of a LinkedIn account logged in to Sales Navigator.

| Field | Type | Required | Description |
|---|---|---|---|
| `li_at` | string | yes | Value of the `li_at` cookie (LinkedIn session). Required. Min length 1. |
| `li_a` | string | no | Value of the `li_a` cookie (Sales Navigator session). |
| `jsessionid` | string | no | Accepted but ignored. Send `JSESSIONID` in `additionnalsCookie`. |
| `ua` | string | no | User agent of the browser the cookies come from. |
| `additionnalsCookie` | array of object or string | no | Other linkedin.com cookies of the same session (spelling with two n is the real field name). `li_at`, `li_a` and `JSESSIONID` entries are dropped (the latter is then lost). |
| `viewport` | object | no | Window size of the browser the cookies come from. Both values required when the object is sent. |
| `viewport.width` | number | yes |  |
| `viewport.height` | number | yes |  |
| `country` | string | no | ISO 3166-1 alpha-2 code of the country the account is used from (for example `FR`). Emelia browses LinkedIn through a proxy located in this country. |

```json
{
  "li_at": "AQEDAFakeLiAtCookieForDocsOnly0000000000",
  "li_a": "AQJ2FakeLiACookieForDocsOnly0000000000",
  "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
  "additionnalsCookie": [
    {
      "name": "JSESSIONID",
      "value": "\"ajax:0000000000000000000\"",
      "domain": ".www.linkedin.com"
    },
    {
      "name": "lang",
      "value": "v=2&lang=fr-fr",
      "domain": ".linkedin.com"
    },
    {
      "name": "bcookie",
      "value": "\"v=2&00000000-fake-0000-0000-000000000000\"",
      "domain": ".linkedin.com"
    }
  ],
  "viewport": {
    "width": 1440,
    "height": 900
  },
  "country": "FR"
}
```

## Example request

**cURL**

```bash
curl -X POST "https://api.emelia.io/linkedin-scrappers/authes" \
  -H "Authorization: $EMELIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "li_at": "AQEDAFakeLiAtCookieForDocsOnly0000000000",
  "li_a": "AQJ2FakeLiACookieForDocsOnly0000000000",
  "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
  "additionnalsCookie": [
    {
      "name": "JSESSIONID",
      "value": "\"ajax:0000000000000000000\"",
      "domain": ".www.linkedin.com"
    },
    {
      "name": "lang",
      "value": "v=2&lang=fr-fr",
      "domain": ".linkedin.com"
    },
    {
      "name": "bcookie",
      "value": "\"v=2&00000000-fake-0000-0000-000000000000\"",
      "domain": ".linkedin.com"
    }
  ],
  "viewport": {
    "width": 1440,
    "height": 900
  },
  "country": "FR"
}'
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/linkedin-scrappers/authes', {
  method: 'POST',
  headers: { Authorization: process.env.EMELIA_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "li_at": "AQEDAFakeLiAtCookieForDocsOnly0000000000",
    "li_a": "AQJ2FakeLiACookieForDocsOnly0000000000",
    "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
    "additionnalsCookie": [
      {
        "name": "JSESSIONID",
        "value": "\"ajax:0000000000000000000\"",
        "domain": ".www.linkedin.com"
      },
      {
        "name": "lang",
        "value": "v=2&lang=fr-fr",
        "domain": ".linkedin.com"
      },
      {
        "name": "bcookie",
        "value": "\"v=2&00000000-fake-0000-0000-000000000000\"",
        "domain": ".linkedin.com"
      }
    ],
    "viewport": {
      "width": 1440,
      "height": 900
    },
    "country": "FR"
  }),
});
console.log(await response.json());
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.emelia.io/linkedin-scrappers/authes",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
    json={
        "li_at": "AQEDAFakeLiAtCookieForDocsOnly0000000000",
        "li_a": "AQJ2FakeLiACookieForDocsOnly0000000000",
        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
        "additionnalsCookie": [
            {
                "name": "JSESSIONID",
                "value": "\"ajax:0000000000000000000\"",
                "domain": ".www.linkedin.com"
            },
            {
                "name": "lang",
                "value": "v=2&lang=fr-fr",
                "domain": ".linkedin.com"
            },
            {
                "name": "bcookie",
                "value": "\"v=2&00000000-fake-0000-0000-000000000000\"",
                "domain": ".linkedin.com"
            }
        ],
        "viewport": {
            "width": 1440,
            "height": 900
        },
        "country": "FR"
    },
)
print(response.json())
```

## Responses

### 200: Account connected.

| Field | Type | Required | Description |
|---|---|---|---|
| `success` | boolean | yes |  |
| `authId` | string | yes | Account id. |

```json
{
  "success": true,
  "authId": "66e0f1a2b3c4d5e6f7a8b9c0"
}
```

### 400: Invalid body or cookie already registered.

| 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"
}
```

### 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"
}
```
