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

# LinkedIn lead form sync

> Copy new LinkedIn Lead Gen Form responses into a lead dataset.

LinkedIn lead form sync reads responses from the forms you declare and writes
each response as a lead in your lead dataset. Erdo checks every 15 minutes.
The capability is behind a feature flag that is off by default; staff
users have it enabled. The dataset must have [lead identity](https://docs.erdo.ai/lead-identity.md)
turned on.

## Declare a source

Use `PUT /v1/provider-lead-sources` to replace the declaration for one provider
and ad account. `dataset` is the dataset slug. `provider` is currently
`linkedin_ads`. `connection_id` is the connected LinkedIn Ads integration
account id. `account_id` is the numeric LinkedIn ad account id. `forms` is the
list of LinkedIn lead gen form ids. `enabled` is optional and defaults to
`true`.

A form must belong to the named ad account. Erdo refuses the save when it does
not, and when another organization has already declared the same form. Use this
preview with accounts dedicated to your organization: a form declaration does
not establish ownership within an account shared by multiple customers.
Responses from any other account or any other form are never written.

```bash theme={null}
curl -X PUT "https://api.erdo.ai/v1/provider-lead-sources" \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "dataset": "northline.leads",
        "provider": "linkedin_ads",
        "connection_id": "00000000-0000-4000-8000-000000000123",
        "account_id": "123456789",
        "forms": ["987654321", "987654322"]
      }'
```

Discover form IDs through `GET /v1/provider-lead-sources/forms?connection_id=<connector-account-id>&account_id=<ad-account-id>`.

Read declarations you can view, with optional provider and account filters:

```bash theme={null}
curl "https://api.erdo.ai/v1/provider-lead-sources?provider=linkedin_ads&account_id=123456789" \
  -H "Authorization: Bearer $ERDO_API_KEY"
```

Delete a declaration by provider and account. Both query parameters are
required for this operation:

```bash theme={null}
curl -X DELETE "https://api.erdo.ai/v1/provider-lead-sources?provider=linkedin_ads&account_id=123456789" \
  -H "Authorization: Bearer $ERDO_API_KEY"
```

The CLI provides the same operations:

```bash theme={null}
curl "https://api.erdo.ai/connectors/my-connections" \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "X-Organization-ID: $ERDO_ORG"
erdo provider-lead-sources forms --connection-id <connector-account-id> --account-id 123456789
erdo provider-lead-sources list --provider linkedin_ads --account-id 123456789
erdo provider-lead-sources set --dataset northline.leads --connection-id 00000000-0000-4000-8000-000000000123 --account-id 123456789 --form 987654321
erdo provider-lead-sources set --file ./provider-lead-source.json
erdo provider-lead-sources delete --provider linkedin_ads --account-id 123456789
```

Repeat `--form` for more forms. The file form accepts one JSON object, and
`--file -` reads it from standard input. Use `--disabled` with flag input to
save the declaration disabled. The `forms` command's `--connection-id` is the
connector account id, not the native integration id. Get it from the
`account_id` field of the matching connection in `GET /connectors/my-connections`
(the response may also include `integration_id`, which is a different id). Use
that connector account id with the numeric LinkedIn ad account id to discover
available form ids, names, states and version ids.

## Backfill and progress

The first sync of a newly declared form backfills its available responses,
including older responses still retained by LinkedIn. Adding a form later also
backfills that form. Moving a declaration to another dataset does not backfill
the forms again; new responses continue into the selected dataset.

Responses are fetched in pages. Erdo advances through them progressively and
stores progress so an interrupted run can resume instead of starting over.
Explicit checkbox answers populate the existing consent columns. A combined
yes requires all returned consent checkboxes to be accepted. No consent answers
means no consent stamp; a later declined answer never downgrades a recorded yes.

If the declared dataset is missing, Erdo disables that source so it cannot keep
retrying writes to a dataset that no longer exists. After three consecutive
sync failures, Erdo raises an attention item for the organization. The source's
`last_error` and `last_run_at` remain available from `list` to diagnose the
failure.

A response's answers are read against the exact lead form version it was
submitted against. If you edited a form, LinkedIn may no longer return the
earlier version, and those responses cannot be read — Erdo records them and
raises one attention item naming them, rather than retrying forever. Later
responses on the current version are unaffected and keep arriving; the answers
for the recorded ones are still downloadable from LinkedIn Campaign Manager.

## Sync timing and deduplication

The first sync looks back 90 days, which is the period for which LinkedIn keeps
responses. Later syncs reread the last 48 hours so late responses are caught.
A response is written once even when Erdo reads it more than once.

## Columns in a lead row

Every lead row gets `email`, `phone`, `first_name`, `last_name`, and the other
LinkedIn profile fields the form asks for. Profile field names are lowercase
snake case, such as `job_title`, `company_name` and `work_email`. The location
fields are `location_city`, `location_state`, `location_country` and
`postal_code`, because `city` and `country` already record where a visitor's
browser was. When a form asks only for a work email or a work phone, that
value also fills `email` or `phone` so the lead can be reached.

Custom questions use a column named from the question name, converted to
lowercase with underscores. If that name would collide with a reserved column,
Erdo prefixes it with `question_`.

The hidden fields `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`,
`utm_content`, `utm_id`, `linkedin_campaign_id`, `linkedin_ad_set_id`,
`linkedin_ad_id`, and `linkedin_account_id` are copied when the form contains a
static value. Dynamic `{{CAMPAIGN_ID}}`, `{{CREATIVE_ID}}`, and `{{ACCOUNT_ID}}`
values are filled from the response. Other dynamic values are left blank, not
written as literal tokens. The campaign, ad, and account ids are always filled
from the response itself.

The row also contains `provider_lead_id`, the LinkedIn response id,
`provider_form`, the versioned form URN, and `source`, which is always
`linkedin_lead_gen_form`. `submitted_at` is when Erdo wrote the row and is the
timestamp that alerts and automations key on. `ts` is when the person
submitted on LinkedIn. `is_test` is LinkedIn's test lead flag.

## Checking a source

Each declaration in the list response carries `synced_through`, the submission
time every earlier response has been handled through, `last_run_at`, and
`last_error`, which says why the last sync did not finish (for example a
connection that needs reconnecting). A response that could not be written is
read again on the next sync. A response whose form asked for neither an email
nor a phone identifies nobody and is not written.

Deleting a declaration leaves the leads already written in place. Declaring the
same account again never writes a response a second time.

## Requirements

The LinkedIn Ads connection must have LinkedIn's Lead Sync permission,
`r_marketing_leadgen_automation`. The member who connects it also needs a
qualifying role on the ad account and its company page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.