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

# LinkedIn

> Connect your own LinkedIn OAuth app to Serval to read member profiles, manage Company Pages, publish posts, pull ads analytics, and manage Matched Audiences from workflows.

## About LinkedIn

LinkedIn is a professional network with REST APIs covering member profiles, organization (Company) pages, advertising, and Matched Audiences. Serval connects through a bring-your-own-app model: your team registers its own app in the [LinkedIn Developer portal](https://www.linkedin.com/developers/apps) and supplies that app's Client ID and Primary Client Secret in Serval's Connect modal. Because Serval does not own a shared LinkedIn app, your team controls exactly which LinkedIn products, permissions, and rate-limit budget the connection uses. Workflow requests go only to LinkedIn's official API host (api.linkedin.com). The integration currently carries a **Beta** badge in Serval's Apps list.

**Authentication:** Bring-your-own LinkedIn app using the [OAuth 2.0 authorization code flow](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow). You pick which permissions to request, register Serval's redirect URL on your app, and enter your Client ID and Primary Client Secret.

**Data sync:** On demand only - there is no background sync. Workflows call the LinkedIn REST API at run time, and Serval injects and automatically refreshes the access token for you (LinkedIn access tokens last about 60 days by default; refresh tokens, where available, about 365 days).

## What the LinkedIn integration enables

| Capability                        | Description                                                                                                                                                                                                                                                                  |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Read member profile               | Fetch the connected member's basic profile (name, headline, vanity name, profile picture). Works on every connection because the Sign In with LinkedIn permission is always included.                                                                                        |
| Check 1st-degree connection count | Look up the connected member's total number of 1st-degree connections. Requires the Connection insights permission plus enrollment in LinkedIn's Marketing Developer Platform program.                                                                                       |
| List administered organizations   | List the organizations (Company Pages) the connected member has a role on, filterable by role and approval state. Requires a [Community Management API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/community-management-overview) permission. |
| Discover ad accounts              | Find the ad accounts the connected member can act on, including the account identifiers used by other advertising calls. Requires an [Advertising API](https://learn.microsoft.com/en-us/linkedin/marketing/) permission.                                                    |
| Fetch ads analytics               | Pull advertising analytics across accounts, campaigns, or creatives, with daily, monthly, yearly, or all-time granularity. Requires the Advertising API reporting permission.                                                                                                |
| Publish posts                     | Create posts as the member or as an organization, with control over visibility, draft versus published state, and resharing. Requires Share on LinkedIn for member posts or Community Management API for organization posts.                                                 |
| Manage Matched Audiences          | Create, read, update, and delete Matched Audience (DMP) segments used for ad targeting. Requires the Matched Audiences permission.                                                                                                                                           |

Anything defined in the [LinkedIn API](https://learn.microsoft.com/en-us/linkedin/) can be accessed through Serval.

## Get your credentials

You need your own LinkedIn OAuth app with the right products approved, its scopes enabled, and Serval's redirect URL registered. Everything happens in the [LinkedIn Developer portal](https://www.linkedin.com/developers/apps).

<Steps>
  <Step title="Create (or open) your LinkedIn app">
    Go to the [LinkedIn Developer portal](https://www.linkedin.com/developers/apps) and click **Create app**, or open an existing app. Associate a LinkedIn Company Page - LinkedIn gates many products on the app being linked to a verified Company Page.
  </Step>

  <Step title="Request the products you need">
    On the **Products** tab, click **Request access** next to the products matching the permissions you will select in Serval (Share on LinkedIn, Advertising API, Community Management API, and so on). LinkedIn must approve each product before its permissions work - some are auto-approved, others are reviewed and can take days. Connect Serval only after the products show under **Added products**.
  </Step>

  <Step title="Approve the OAuth scopes">
    On the **Auth** tab, under **OAuth 2.0 scopes**, approve the scopes for the products you added. You will pick the matching subset in Serval's Connect modal - Serval only requests what you select.
  </Step>

  <Step title="Register Serval's redirect URL">
    Still on the **Auth** tab, under **Authorized redirect URLs for your app**, paste the exact Redirect URL shown in Serval's Connect modal and click **Update**. The URL is environment specific - always use the value from the modal, never a hand-constructed one.
  </Step>

  <Step title="Copy your credentials">
    From the **Auth** tab, copy the **Client ID** and the **Primary Client Secret** into Serval's Connect modal.
  </Step>
</Steps>

<Warning>
  Do not click **Generate a new Client Secret** on LinkedIn after connecting. LinkedIn invalidates the previous secret immediately, which breaks Serval's automatic token refresh until you reconnect with the fresh secret.
</Warning>

<Note>
  LinkedIn uses a two-layer permission model: products (Products tab) grant the right to use scopes (Auth tab). A scope can be approved and still fail until the backing product appears under **Added products**.
</Note>

## Connect in Serval

The Connect modal has two steps: **Permissions**, then **Connect**.

<Steps>
  <Step title="Step 1: Permissions - choose what Serval requests">
    Check the permission bundles that match the products your LinkedIn app has. **Sign In with LinkedIn** is shown checked and disabled with the tag "Always included" - it is requested on every connection. The bundles are convenience groupings, not a strict one-to-one product map (some scopes are granted by more than one LinkedIn product), so you can also expand **All permissions** and search individual scopes (search placeholder: "Search all LinkedIn permissions...") to add a single scope without taking a whole bundle. Serval requests only what you select; an unrecognized scope is rejected with "unsupported LinkedIn scope: \[scope]".

    | Permission bundle                       | LinkedIn scopes requested                                                                         |
    | --------------------------------------- | ------------------------------------------------------------------------------------------------- |
    | Sign In with LinkedIn (always included) | `r_basicprofile`                                                                                  |
    | Share on LinkedIn                       | `w_member_social`                                                                                 |
    | Advertising API                         | `r_ads`, `rw_ads`, `r_ads_reporting`                                                              |
    | Community Management API                | `r_organization_admin`, `rw_organization_admin`, `r_organization_social`, `w_organization_social` |
    | Matched Audiences                       | `rw_dmp_segments`                                                                                 |
    | Connection insights                     | `r_1st_connections_size`                                                                          |

    <Warning>
      Only select permissions your LinkedIn app actually has enabled. Requesting even one scope the app lacks fails the entire authorization with LinkedIn's `unauthorized_scope_error`.
    </Warning>
  </Step>

  <Step title="Step 2: Connect - register the redirect URL">
    The modal displays a read-only **Redirect URL** with a **Copy** button (it toggles to "Copied"). Helper text: "Add this as an Authorized redirect URL in your LinkedIn app's Auth tab." Register it on your LinkedIn app before continuing.
  </Step>

  <Step title="Enter your connection details">
    All three fields are required: the **Connect to LinkedIn** button stays disabled until every field is filled, the form refuses to submit without them ("Please fill in all required fields"), and blank values are also rejected server-side ("appName is required", "clientId is required", "clientSecret is required").

    * **Connection name** (required) - a Serval-side label only. Helper text: "The name that will identify this connection in Serval. We recommend using your LinkedIn app name." LinkedIn does not expose your app's display name to Serval, so you choose it.
    * **Client ID** (required) - from your LinkedIn app's Auth tab.
    * **Primary Client Secret** (required, hidden password field) - from your LinkedIn app's Auth tab.
  </Step>

  <Step title="Authorize on LinkedIn">
    Click **Connect to LinkedIn** and approve the consent screen. Complete it within 10 minutes - Serval holds your pending credentials only that long, after which you will see "Invalid or expired OAuth state" or "OAuth session expired, please try again" and need to restart the flow.
  </Step>
</Steps>

<Note>
  There is no separate edit-credentials form. To update the secret or pick up newly approved permissions, run the Connect flow again with the same Client ID - the existing connection is updated in place (the connection is keyed on the Client ID), your workflows are preserved, and the permission picker is pre-seeded with the scopes the connection currently has so you do not accidentally downgrade. A different Client ID creates a separate connection.
</Note>

## Verifying the connection

The LinkedIn integration ships three health checks:

* **Test LinkedIn Connection** - verifies Serval can authenticate by fetching the connected member's basic profile.
  * Success: "Successfully connected to LinkedIn as \[name]"
  * Failure: "Unable to connect to LinkedIn. Verify your OAuth credentials are valid, the app has the r\_basicprofile scope approved, and the token has not been revoked."
* **Get 1st-degree Connection Count** - verifies the Connection insights permission by fetching the member's total 1st-degree connections.
  * Success: "Authenticated member has \[number] 1st-degree connection(s)." or, when LinkedIn omits the count, "API access verified but LinkedIn did not return a connection count. The scope is approved but the member may not have any connections exposed."
  * Product-gating failure: "Your LinkedIn app has the r\_1st\_connections\_size scope approved, but the /v2/connections endpoint requires the app to also be enrolled in the LinkedIn Marketing Developer Platform program. Apply for the program from your LinkedIn app's Products tab - once approved, this check will pass without needing to reconnect."
  * Generic failure: "Unable to fetch connection count. Verify your LinkedIn app has the r\_1st\_connections\_size scope approved on the Auth tab."
* **List Administered Organizations** - verifies the organization-admin permission by listing organizations the member administers.
  * Success: "Found \[number] administered organization(s)." or, with zero results, "API access verified but this member does not administer any organizations."
  * Failure: "Unable to list organizations. Verify your LinkedIn app has the r\_organization\_admin or rw\_organization\_admin scope approved."

<Tip>
  A green **Test LinkedIn Connection** with failures elsewhere almost always means product gating: basic profile access works on every live connection, but other calls fail until the backing LinkedIn product is approved under **Added products**. Once LinkedIn approves the product, the failing checks pass without reconnecting.
</Tip>

## Gotchas and troubleshooting

<AccordionGroup>
  <Accordion title="A permission is approved but calls still fail with a 403">
    LinkedIn products gate scopes. A scope checked on the Auth tab appears on the consent screen and in the granted list, yet calls return 403 until the backing product is under **Added products** on the Products tab. The clearest example: the connection-count check fails with LinkedIn's ACCESS\_DENIED error when the Connection insights scope is approved but the app is not enrolled in the Marketing Developer Platform program. Apply for the missing product from the Products tab - once approved, calls succeed without reconnecting.
  </Accordion>

  <Accordion title="Authorization fails with unauthorized_scope_error">
    You selected a permission in step 1 that your LinkedIn app does not have enabled. LinkedIn fails the entire authorization, not just the one scope. Restart the Connect flow and select only the bundles matching the products your app actually has, or enable the missing scope on the app's Auth tab first.
  </Accordion>

  <Accordion title="Invalid or expired OAuth state / OAuth session expired">
    Serval holds the credentials you submit in the Connect modal for only 10 minutes between clicking **Connect to LinkedIn** and finishing LinkedIn's consent screen. Taking longer shows "Invalid or expired OAuth state" or "OAuth session expired, please try again". Just restart the Connect flow.
  </Accordion>

  <Accordion title="LinkedIn shows an invalid_client error">
    The Client ID or Primary Client Secret does not match a live LinkedIn app credential. Re-copy both values from your app's Auth tab - and if someone clicked **Generate a new Client Secret**, the old secret is already invalid and you must reconnect with the new one.
  </Accordion>

  <Accordion title="Token refresh stopped working after rotating the client secret">
    Serval refreshes access tokens using the Client ID and secret stored with the connection. Generating a new Client Secret on LinkedIn invalidates the previous one immediately, so refresh fails until you reconnect with the fresh secret. Reconnecting with the same Client ID updates the existing connection in place. Note that refresh tokens only exist when your app is enrolled in LinkedIn's refresh token program; otherwise reconnect when the access token expires (about 60 days).
  </Accordion>

  <Accordion title="Advertising API: Development vs Standard tier">
    LinkedIn grants the Advertising API at a Development tier first, with tighter limits on accessible ad accounts and request volume. If advertising calls work in testing but hit limits in production, apply for the Standard tier from your app's Products tab on LinkedIn. This is LinkedIn-side behavior and approval is at LinkedIn's discretion.
  </Accordion>

  <Accordion title="Older ad-account lookups no longer work">
    LinkedIn retired its legacy ad-account discovery calls, which now return errors. Use the integration's ad-account discovery capability instead, which runs on LinkedIn's current versioned Marketing API. Serval automatically attaches the protocol and version headers LinkedIn's versioned API requires, so you never set them yourself.
  </Accordion>

  <Accordion title="Matched Audiences quirks">
    When a workflow creates a Matched Audience segment, LinkedIn returns the new segment's ID in a response header rather than the response body, and update or delete operations return an empty success response. Only the segment's name, description, access policy, and source segment can be updated. Populating a segment with members uses LinkedIn's separate CSV list-upload process, which is not available through this integration's API action.
  </Accordion>
</AccordionGroup>

***

Need help? Contact **[support@serval.com](mailto:support@serval.com)** for assistance with your LinkedIn integration.
