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

# Migrate to Incremental Mode

> Implement incremental sync for Extract LinkedIn Connections with Engagement Identities, and migrate existing full-mode schedules to incremental.

This guide shows how to use the **scheduled run endpoint** for [Extract LinkedIn Connections](/v1/api/actions/linkedin-extract-connections-schedule) with **incremental sync** via Engagement Identities, and how to migrate existing schedules to incremental mode using the `/actions/linkedin-extract-connections/run/schedule` route.

Incremental mode fetches **only new data** since the last retrieval instead of the full dataset on every run. It is available only on some [engagement-free actions](/v1/identities/engagement#included-actions) and requires an Engagement Identity.

More information here : [Incremental Sync Guide](https://docs.edges.run/v1/runs/overview#incremental-sync-mode)

***

## Prerequisites

* [Engagement Identities](/v1/identities/engagement) enabled in your workspace and at least one Engagement Identity created with a connected LinkedIn account
* API key with access to runs, schedules, and identities

***

## Shared setup (reuse in your code)

The examples below use a single base URL and a small request helper so headers and error handling stay consistent. Define these once and reuse them across Part 1 and Part 2.

```javascript theme={null}
const EDGES_API_BASE = 'https://api.edges.run/v1';

async function edgesRequest(apiKey, url, { method = 'GET', body } = {}) {
  const res = await fetch(url, {
    method,
    headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
    ...(body != null && { body: JSON.stringify(body) }),
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? `Request failed: ${res.status}`);
  }
  return res.json();
}
```

***

## Part 1: Implementing Incremental Mode

### 1.1 Create a new scheduled run in incremental mode

To schedule **Extract LinkedIn Connections** in incremental mode, send `sync_mode: "incremental"` in the `parameters` object and use an Engagement Identity in `identity_ids`.

**Behavior:**

* **First run:** Full sync (capped by `max_results`).
* **Next runs:** Only new connections since the last run. No need to call continue; each scheduled execution is automatically incremental.

<Tip>
  In schedule mode, if the previous run is still in progress when the next iteration is due, that iteration is **skipped**. The following one will run the incremental update.
</Tip>

**JavaScript example: create a scheduled incremental Extract Connections run**

```javascript theme={null}
// Uses EDGES_API_BASE and edgesRequest from the shared setup above.

async function createIncrementalExtractConnectionsSchedule(apiKey, options) {
  const {
    identityId,
    linkedinUrl,
    callbackUrl,
    cron = '0 9 * * *',      // daily at 9am
    timezone = 'Europe/Paris',
    maxResults,
  } = options;

  return edgesRequest(apiKey, `${EDGES_API_BASE}/actions/linkedin-extract-connections/run/schedule`, {
    method: 'POST',
    body: {
      inputs: [{ linkedin_url: linkedinUrl }],
      callback: { url: callbackUrl },
      identity_ids: [identityId],
      cron,
      timezone,
      parameters: {
        sync_mode: 'incremental',
        ...(maxResults != null && { max_results: maxResults }),
      },
    },
  });
}

// Usage: ensure identityId is an Engagement Identity (type === 'engagement')
// const schedule = await createIncrementalExtractConnectionsSchedule(process.env.EDGES_API_KEY, {
//   identityId: 'id_xxx',
//   linkedinUrl: 'https://linkedin.com/in/johndoe',
//   callbackUrl: 'https://yourdomain.com/webhooks/edges',
// });
```

### 1.2 Async mode: continue an incremental run

If you use **async** instead of schedule, you trigger each incremental update by calling the continue endpoint after the previous run has finished.

**JavaScript example: run async and continue later (Extract Connections)**

```javascript theme={null}
// Uses EDGES_API_BASE and edgesRequest from the shared setup above.

async function runExtractConnectionsIncrementalAsync(apiKey, options) {
  return edgesRequest(apiKey, `${EDGES_API_BASE}/actions/linkedin-extract-connections/run/async`, {
    method: 'POST',
    body: {
      inputs: [{ linkedin_url: options.linkedinUrl }],
      callback: { url: options.callbackUrl },
      identity_ids: [options.identityId],
      parameters: {
        sync_mode: 'incremental',
        ...(options.maxResults != null && { max_results: options.maxResults }),
      },
    },
  });
}

async function continueIncrementalRun(apiKey, runUid) {
  return edgesRequest(apiKey, `${EDGES_API_BASE}/runs/${runUid}/continue`, { method: 'POST' });
}
```

***

## Part 2: Migrating full-mode schedules to incremental

Schedules cannot be updated in place (e.g. to add `parameters.sync_mode`). To switch existing **Extract Connections** schedules from full to incremental, you must:

1. List schedules for `linkedin-extract-connections` (optionally restricted to Engagement Identities).
2. For each schedule: get full details, cancel it, then create a new schedule with the same config plus `parameters.sync_mode: "incremental"`.

Use only **Engagement Identities** for the new schedules; incremental sync is not supported for standard identities.

Schedule objects in the examples have optional fields such as `scheduled_run_uid`, `uid`, `identity_ids`, `inputs`, `callback`, `cron`, `timezone`, `parameters`, `max_results`.

### 2.1 Get Engagement Identity IDs

Filter identities with `type: "engagement"` so you only migrate schedules that use them (or only recreate schedules using those identities). The helper below paginates so all identities are returned (not only the first 100).

```javascript theme={null}
// Uses EDGES_API_BASE and edgesRequest from the shared setup above.

async function getEngagementIdentityIds(apiKey) {
  const ids = [];
  let offset = 0;
  const limit = 100;
  let hasMore = true;
  while (hasMore) {
    const data = await edgesRequest(
      apiKey,
      `${EDGES_API_BASE}/identities?limit=${limit}&offset=${offset}`
    );
    const items = data.data ?? data.items ?? (Array.isArray(data) ? data : []);
    ids.push(...items.filter((id) => id.type === 'engagement').map((id) => id.uid));
    hasMore = items.length === limit;
    offset += limit;
  }
  return ids;
}
```

### 2.2 List Extract Connections schedules (full mode)

List schedules for the Extract Connections action. The helper paginates so all matching schedules are returned.

```javascript theme={null}
// Uses EDGES_API_BASE and edgesRequest from the shared setup above.

async function listExtractConnectionsSchedules(apiKey) {
  const schedules = [];
  let offset = 0;
  const limit = 100;
  let hasMore = true;
  while (hasMore) {
    const params = new URLSearchParams({
      action_name: 'linkedin-extract-connections',
      status: 'ACTIVE',
      limit: String(limit),
      offset: String(offset),
    });
    const data = await edgesRequest(apiKey, `${EDGES_API_BASE}/schedules?${params}`);
    const items = data.data ?? data.items ?? (Array.isArray(data) ? data : []);
    schedules.push(...items);
    hasMore = items.length === limit;
    offset += limit;
  }
  return schedules;
}
```

### 2.3 Filter by Engagement Identity (optional)

If the list response includes `identity_ids` (or similar), keep only schedules whose identities are in your set of Engagement Identity UIDs. If the API does not return identity info, you can still migrate all Extract Connections schedules and rely on the fact that the **new** schedule you create will use an Engagement Identity.

```javascript theme={null}
function filterSchedulesByEngagementIdentities(schedules, engagementIdentityIds) {
  const set = new Set(engagementIdentityIds);
  return schedules.filter((s) => {
    const ids = s.identity_ids ?? [];
    return ids.some((id) => set.has(id));
  });
}
```

### 2.4 Cancel and recreate each schedule with incremental mode

For each schedule to migrate:

1. **GET** the schedule by `scheduled_run_uid` to get `inputs`, `callback`, `cron`, `timezone`, `identity_ids`, etc.
2. **POST** `.../schedules/{scheduled_run_uid}/cancel` to delete it.
3. **POST** `.../actions/linkedin-extract-connections/run/schedule` with the same config plus `parameters: { sync_mode: 'incremental' }`.

Migration helpers (use `edgesRequest` from the shared setup):

```javascript theme={null}
async function getSchedule(apiKey, scheduledRunUid) {
  return edgesRequest(apiKey, `${EDGES_API_BASE}/schedules/${scheduledRunUid}`);
}

async function cancelSchedule(apiKey, scheduledRunUid) {
  return edgesRequest(apiKey, `${EDGES_API_BASE}/schedules/${scheduledRunUid}/cancel`, {
    method: 'POST',
  });
}

async function migrateScheduleToIncremental(apiKey, scheduledRunUid) {
  const schedule = await getSchedule(apiKey, scheduledRunUid);
  await cancelSchedule(apiKey, scheduledRunUid);

  const body = {
    inputs: schedule.inputs ?? [],
    callback: schedule.callback ?? {},
    identity_ids: schedule.identity_ids ?? [],
    ...(schedule.cron && { cron: schedule.cron }),
    ...(schedule.timezone && { timezone: schedule.timezone }),
    ...(schedule.schedule_at && { schedule_at: schedule.schedule_at }),
    parameters: {
      // Add parameters if needed manually, because you can't get back parameters from scheduled runs
      sync_mode: 'incremental',
    },
    // Add max_results if needed manually
  };

  return edgesRequest(apiKey, `${EDGES_API_BASE}/actions/linkedin-extract-connections/run/schedule`, {
    method: 'POST',
    body,
  });
}
```

<Note>
  If you don't need to get older results with the first incremental mode. Keep the max\_results low to avoid time consumption
</Note>

### 2.5 Full migration script (Extract Connections + Engagement Identities)

End-to-end example: list active Extract Connections schedules, restrict to those using Engagement Identities, then migrate each to incremental by cancel + recreate. Runs **sequentially** to avoid rate limits.

```javascript theme={null}
// Uses EDGES_API_BASE, edgesRequest, and all helpers above.

async function migrateExtractConnectionsSchedulesToIncremental(apiKey) {
  const engagementIds = await getEngagementIdentityIds(apiKey);
  if (engagementIds.length === 0) {
    console.warn('No Engagement Identities found. Incremental mode requires them.');
    return [];
  }

  const schedules = await listExtractConnectionsSchedules(apiKey);
  const toMigrate = filterSchedulesByEngagementIdentities(schedules, engagementIds);

  const results = [];
  for (const s of toMigrate) {
    const uid = s.scheduled_run_uid ?? s.uid;
    if (!uid) continue;
    try {
      const newSchedule = await migrateScheduleToIncremental(apiKey, uid);
      results.push({ uid, newSchedule });
      console.log(`Migrated schedule ${uid}`);
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e);
      results.push({ uid, error: message });
      console.error(`Failed to migrate ${uid}:`, message);
    }
  }
  return results;
}

// Run migration
migrateExtractConnectionsSchedulesToIncremental(process.env.EDGES_API_KEY)
  .then((results) => console.log('Migration results:', results))
  .catch((err) => console.error('Migration failed:', err));
```

***

## Summary

| Goal                                           | Approach                                                                                                                                                                                                 |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **New incremental schedule**                   | `POST /v1/actions/linkedin-extract-connections/run/schedule` with `parameters.sync_mode: "incremental"` and an Engagement Identity in `identity_ids`.                                                    |
| **Async incremental**                          | Same `parameters` on async run; then call `POST /v1/runs/{run_uid}/continue` for each subsequent fetch.                                                                                                  |
| **Existing full-mode schedules → incremental** | List schedules (`GET /v1/schedules?action_name=linkedin-extract-connections`), filter by Engagement Identity if needed, then for each: GET schedule → cancel → recreate with `sync_mode: "incremental"`. |

For more on incremental sync behavior and limits, see the [Incremental Sync guide](/v1/runs/overview#incremental-sync-mode) and [Continue a run](/v1/api/runs/continue).
