# Audience Syncs

> Mirror contact lists to ad platforms (Meta, LinkedIn, Google Ads, X) on a recurring schedule

Source: https://docs.graph8.com/developers/audience-syncs/

> **Tip: Using the SDK or CLI?**
> The [JavaScript SDK](https://docs.graph8.com/developers/sdk/) and [CLI](https://docs.graph8.com/developers/cli/) both wrap these endpoints. The CLI commands are `g8 audience-syncs list`, `g8 audience-syncs create`, and `g8 audience-syncs trigger`.

Audience syncs push a graph8 audience (contact list) to an ad platform and keep it in sync on a cadence. graph8 supports four platforms: **Meta** (Facebook/Instagram custom audiences), **LinkedIn** (Matched Audiences), **Google Ads** (Customer Match), and **X** (Tailored Audiences).

Each sync config has a **mode**:

- `mirror` - keep the platform audience exactly in sync with the source list (adds new members, removes dropped members).
- `append_only` - only add new members; never remove.

A worker re-runs each sync on its `refresh_cadence_hours`. You can also trigger a sync manually.

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/audience-syncs` | GET | List all sync configs for the org |
| `/audience-syncs` | POST | Create a new sync config |
| `/audience-syncs/{id}` | GET | Get a single sync config |
| `/audience-syncs/{id}` | PATCH | Update a sync config |
| `/audience-syncs/{id}` | DELETE | Soft delete a sync config |
| `/audience-syncs/{id}/trigger` | POST | Manually trigger a sync run |
| `/audience-syncs/{id}/runs` | GET | List recent sync runs |
| `/audience-syncs/{id}/errors` | GET | List failed sync runs only |

For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs).

---

## List Audience Syncs

`GET /audience-syncs`

Returns every sync config for the authenticated org.

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/audience-syncs" \
  -H "Authorization: Bearer $API_KEY"
```

**Python**

```python
response = requests.get(
    f"{BASE_URL}/audience-syncs",
    headers=HEADERS,
)
configs = response.json()["data"]
```

### Response

```json
{
  "data": [
    {
      "id": 12,
      "audience_id": 5,
      "platform": "meta",
      "platform_audience_name": "Q2 Outbound - Mirror",
      "mode": "mirror",
      "refresh_cadence_hours": 24,
      "is_active": true,
      "status": "active",
      "last_sync_at": "2026-04-15T08:00:00Z",
      "created_at": "2026-04-01T10:00:00Z"
    }
  ]
}
```

---

## Create Audience Sync

`POST /audience-syncs`

Create a new sync config. Returns `409 Conflict` if a config already exists for the same `audience_id` + `platform`.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `audience_id` | integer | **Yes** | Source list ID (must be > 0) |
| `platform` | string | **Yes** | One of `meta`, `linkedin`, `google`, `x` |
| `platform_audience_name` | string | No | Display name on the ad platform (max 255) |
| `mode` | string | No | `mirror` (default) or `append_only` |
| `refresh_cadence_hours` | integer | No | 0-720, default 24 |
| `platform_config` | object | No | Platform-specific config (e.g. ad account ID, OAuth scopes) |
| `suppression_list_ids` | integer[] | No | Lists whose members should be excluded |

### Example

**cURL**

```bash
curl -X POST "https://be.graph8.com/api/v1/audience-syncs" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audience_id": 5,
    "platform": "meta",
    "platform_audience_name": "Q2 Outbound",
    "mode": "mirror",
    "refresh_cadence_hours": 24,
    "platform_config": {"ad_account_id": "act_1234567890"}
  }'
```

**Python**

```python
response = requests.post(
    f"{BASE_URL}/audience-syncs",
    headers=HEADERS,
    json={
        "audience_id": 5,
        "platform": "meta",
        "platform_audience_name": "Q2 Outbound",
        "mode": "mirror",
        "refresh_cadence_hours": 24,
        "platform_config": {"ad_account_id": "act_1234567890"},
    },
)
```

### Response `201 Created`

Returns the same shape as a list item.

### Errors

| Status | Meaning |
|--------|---------|
| `409` | A sync config already exists for this audience + platform |
| `422` | `platform` not in allowed set, or `mode` invalid |

---

## Get Audience Sync

`GET /audience-syncs/{config_id}`

Returns a single sync config by ID.

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/audience-syncs/12" \
  -H "Authorization: Bearer $API_KEY"
```

### Errors

| Status | Meaning |
|--------|---------|
| `404` | Sync config not found |

---

## Update Audience Sync

`PATCH /audience-syncs/{config_id}`

Partial update. Send only the fields you want to change.

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `mode` | string | `mirror` or `append_only` |
| `refresh_cadence_hours` | integer | 0-720 |
| `is_active` | boolean | Pause/unpause without deleting |
| `suppression_list_ids` | integer[] | Replace suppression list set |

### Example

**cURL**

```bash
curl -X PATCH "https://be.graph8.com/api/v1/audience-syncs/12" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'
```

---

## Delete Audience Sync

`DELETE /audience-syncs/{config_id}`

Soft delete. The platform audience is **not** removed from the ad platform - graph8 simply stops syncing.

### Response

```json
{ "data": { "status": "deleted", "id": 12 } }
```

---

## Trigger Audience Sync

`POST /audience-syncs/{config_id}/trigger`

Run the sync immediately, outside of its scheduled cadence. Returns the sync result payload (members added/removed counts).

### Example

**cURL**

```bash
curl -X POST "https://be.graph8.com/api/v1/audience-syncs/12/trigger" \
  -H "Authorization: Bearer $API_KEY"
```

### Errors

| Status | Meaning |
|--------|---------|
| `500` | Underlying ad platform returned an error - check `/runs` or `/errors` for the message |

---

## List Sync Runs

`GET /audience-syncs/{config_id}/runs`

Returns recent runs for a config (success and failure both).

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | integer | 20 | Max runs to return (1-100) |

### Response

```json
{
  "data": [
    {
      "id": 4521,
      "started_at": "2026-04-15T08:00:00Z",
      "finished_at": "2026-04-15T08:00:14Z",
      "status": "success",
      "members_added": 12,
      "members_removed": 3,
      "total_members": 1450,
      "error_message": null
    }
  ]
}
```

---

## List Sync Errors

`GET /audience-syncs/{config_id}/errors`

Same shape as `/runs`, filtered to `status = "failed"`. Use this when surfacing failures in your dashboard.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | integer | 20 | Max errors to return (1-100) |

### Response

```json
{
  "data": [
    {
      "id": 4520,
      "started_at": "2026-04-15T07:30:00Z",
      "error_message": "Meta API: invalid access token",
      "details": { "config_id": 12, "status": "failed" }
    }
  ]
}
```
