# Tracking pixels

These four endpoints manage a store's tracking pixels, the same list the merchant edits on the dashboard's pixel page (`/dashboard/pixels`). A store can hold seven types of pixel: Meta (Facebook), TikTok, Snapchat, Pinterest, Google Analytics, Google Tag Manager and Google Ads. What each platform needs, and how to check that its events arrive, is in the [pixels guide](https://dzbuild.com/docs/marketing/pixels.md).

The checks are syntactic only. A `201` means the ids are well formed, not that the ad platform accepted them. The server-side access token is write-only: no endpoint returns it, and `has_token` tells you whether one is stored.

## Before you start

* `GET` needs `pixels:read`. `POST`, `PATCH` and `DELETE` need `pixels:write` and an `Idempotency-Key` (see [Idempotency](https://dzbuild.com/api-docs/idempotency.md)). Keys created from the dashboard (**Settings → API**, `/dashboard/api`) carry both scopes. Scopes are frozen when a key is created, so an older key that lacks the one it needs answers `403 forbidden`: create a new key from the dashboard.
* The plan sets how many pixels a store can hold: none on Free, one of each type on Pro, no limit on Unlimited and Enterprise. A personal key works only on a store with an active Enterprise plan. An installed app can also reach stores on Free or Pro, where the limit applies.
* A merchant can assign a pixel to products, categories or landing pages on the dashboard. The API neither reads nor changes these assignments.

| Scope          | Description                                                         |
| -------------- | ------------------------------------------------------------------- |
| `pixels:read`  | Read the store's tracking pixels. Access tokens are never returned. |
| `pixels:write` | Add, update and delete tracking pixels.                             |

## The pixel object

| Field                      | Type           | Notes                                                                                                                   |
| -------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `id`                       | int            | The pixel's id on DZBuild, used in the path of `PATCH` and `DELETE`.                                                    |
| `pixel_type`               | string         | One of the seven types below. Fixed at creation.                                                                        |
| `pixel_id`                 | string         | The pixel, tag or measurement id from the ad platform. Fixed at creation.                                               |
| `pixel_name`               | string or null | Display name.                                                                                                           |
| `has_token`                | bool           | `true` when a server-side access token is stored.                                                                       |
| `test_event_code`          | string or null | The test events code typed on the dashboard. The API cannot set it, and a `PATCH` that writes a field clears it.        |
| `ad_account_id`            | string or null | The Pinterest ad account id.                                                                                            |
| `conversion_label`         | string or null | The Google Ads conversion label.                                                                                        |
| `is_active`                | bool           | `false` keeps the pixel and stops its browser and server-side events.                                                   |
| `is_default`               | bool           | Setting it to `true` clears it on the store's other pixels of the same type. Where a pixel loads does not depend on it. |
| `created_at`, `updated_at` | string         | `YYYY-MM-DD HH:MM:SS`, Algeria time.                                                                                    |

### Pixel types

| `pixel_type`       | Platform           | Server-side events                                              |
| ------------------ | ------------------ | --------------------------------------------------------------- |
| `facebook`         | Meta (Facebook)    | Yes, when the pixel has an access token.                        |
| `tiktok`           | TikTok             | Yes, when the pixel has an access token.                        |
| `snapchat`         | Snapchat           | Yes, when the pixel has an access token.                        |
| `pinterest`        | Pinterest          | Yes, when the pixel has an access token and an `ad_account_id`. |
| `google_analytics` | Google Analytics   | No.                                                             |
| `gtm`              | Google Tag Manager | No.                                                             |
| `google_ads`       | Google Ads         | No. `conversion_label` is read for this type only.              |

A token sent for a type without server-side events is stored and never used.

### Where a pixel loads

An active pixel with no assignments loads on every storefront page, landing pages included. A pixel the merchant assigned to products, categories or landing pages loads only on the matching pages, and its server-side events follow the same rule. A pixel created through the API starts with no assignments.

## The access token

`access_token` is the server-side token copied from the ad platform's events manager. The API removes invisible characters and the spaces and quotes around it, then refuses the token with `422 invalid_access_token` when it still contains `<`, a space or a line break, when it equals `pixel_id`, or when a `facebook` token is shorter than 40 characters.

* No endpoint returns the token. The change history keeps the mask `••••••••` in its place.
* On `PATCH`, an empty string, `null` or a value containing that mask keeps the stored token. A token can be replaced but not removed through the API: to drop it, delete the pixel and add it again without a token, which also drops its assignments.

## `GET /v1/pixels`

The store's pixels, newest first, with the plan allowance in `limits`.

**Auth:** platform key with `pixels:read`.

### Query parameters

| Param        | Type   | Default | Notes                                                                                             |
| ------------ | ------ | ------- | ------------------------------------------------------------------------------------------------- |
| `pixel_type` | string | none    | Only pixels of this type. An unknown type returns an empty list.                                  |
| `limit`      | int    | 50      | 1 to 200.                                                                                         |
| `cursor`     | string | none    | `next_cursor` of the previous page. See [Pagination](https://dzbuild.com/api-docs/pagination.md). |

### Request

```
curl 'https://api.dzbuild.app/v1/pixels?pixel_type=facebook' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Response 200

```
{

  "data": {

    "items": [

      {

        "id": 12,

        "pixel_type": "facebook",

        "pixel_id": "123456789012345",

        "pixel_name": "Main ad account",

        "has_token": true,

        "test_event_code": null,

        "ad_account_id": null,

        "conversion_label": null,

        "is_active": true,

        "is_default": false,

        "created_at": "2026-10-01 14:20:05",

        "updated_at": "2026-10-01 14:20:05"

      }

    ],

    "next_cursor": null,

    "has_more": false,

    "limits": {

      "plan": "enterprise",

      "can_add": true,

      "per_type_limit": null,

      "total_limit": null,

      "counts": {

        "facebook": 1,

        "tiktok": 1,

        "snapchat": 0,

        "pinterest": 0,

        "google_analytics": 1,

        "gtm": 0,

        "google_ads": 0

      },

      "total": 3

    }

  }

}
```

### `limits`

`limits` comes with every page and describes the whole store, whatever `pixel_type` you filter on.

| Field            | Meaning                                                                                                                                 |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `plan`           | The store plan the allowance comes from.                                                                                                |
| `can_add`        | `false` when the plan allows no pixels at all. It ignores the counts, so compare `counts` with `per_type_limit` before you add a pixel. |
| `per_type_limit` | Pixels allowed per type, `null` for no limit.                                                                                           |
| `total_limit`    | Pixels allowed in total, `null` for no limit.                                                                                           |
| `counts`         | Pixels per type, with a key for each of the seven types.                                                                                |
| `total`          | All the store's pixels, active or not.                                                                                                  |

## `POST /v1/pixels`

Adds a pixel and answers `201` with it.

**Auth:** platform key with `pixels:write`. **Requires `Idempotency-Key`.**

### Body

| Field              | Type   | Required | Notes                                                                                                 |
| ------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `pixel_type`       | string | yes      | One of the seven types, in any letter case. `type` is accepted too.                                   |
| `pixel_id`         | string | yes      | 1 to 100 letters, digits, `-` or `_`. A `facebook` id is 15 to 17 digits, copied from Events Manager. |
| `pixel_name`       | string | no       | Cut to 100 characters. `name` is accepted too.                                                        |
| `access_token`     | string | no       | Follows the access token rules above. Omit it or send `""` for a pixel without server-side events.    |
| `ad_account_id`    | string | no       | Cut to 64 characters.                                                                                 |
| `conversion_label` | string | no       | Cut to 64 characters.                                                                                 |
| `is_active`        | bool   | no       | Defaults to `true`.                                                                                   |
| `is_default`       | bool   | no       | Defaults to `false`.                                                                                  |

### What the call checks

The checks run in this order, and the first one that fails gives the error. A refusal is stored against its `Idempotency-Key` for 24 hours: once you fix the cause, send the call again with a new `Idempotency-Key`.

1. `pixel_type` is one of the seven types, otherwise `422 invalid_pixel_type`.
2. `pixel_id` has the right format, otherwise `422 invalid_pixel_id`.
3. The plan allows another pixel of this type, otherwise `409 limit_reached`.
4. The store has no pixel of the same type with the same `pixel_id`, otherwise `409 pixel_exists`.
5. `access_token`, when sent, follows the access token rules, otherwise `422 invalid_access_token`.

### Request

```
curl -X POST 'https://api.dzbuild.app/v1/pixels' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: pixel-meta-main-1" \

  -d '{"pixel_type": "facebook", "pixel_id": "123456789012345", "pixel_name": "Main ad account", "access_token": "EAAGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
```

### Response 201

```
{

  "data": {

    "id": 12,

    "pixel_type": "facebook",

    "pixel_id": "123456789012345",

    "pixel_name": "Main ad account",

    "has_token": true,

    "test_event_code": null,

    "ad_account_id": null,

    "conversion_label": null,

    "is_active": true,

    "is_default": false,

    "created_at": "2026-10-01 14:20:05",

    "updated_at": "2026-10-01 14:20:05"

  }

}
```

## `PATCH /v1/pixels/{id}`

Changes only the fields you send and answers `200` with the pixel. An empty body changes nothing.

**Auth:** platform key with `pixels:write`. **Requires `Idempotency-Key`.**

### Body

| Field              | Type           | Notes                                                                              |
| ------------------ | -------------- | ---------------------------------------------------------------------------------- |
| `pixel_name`       | string or null | Cut to 100 characters. `null` or `""` clears it.                                   |
| `access_token`     | string         | A new token replaces the stored one. An empty string, `null` or the mask keeps it. |
| `ad_account_id`    | string or null | Cut to 64 characters. `null` or `""` clears it.                                    |
| `conversion_label` | string or null | Cut to 64 characters. `null` or `""` clears it.                                    |
| `is_active`        | bool           | `false` pauses the pixel and keeps it.                                             |
| `is_default`       | bool           | `true` clears it on the store's other pixels of the same type.                     |

`pixel_type`, `type` and `pixel_id` cannot be sent, even with their current value: the call answers `422 immutable_field`. To change them, delete the pixel and add a new one.

### Request

```
curl -X PATCH 'https://api.dzbuild.app/v1/pixels/12' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: pixel-12-pause-1" \

  -d '{"is_active": false}'
```

### Response 200

```
{

  "data": {

    "id": 12,

    "pixel_type": "facebook",

    "pixel_id": "123456789012345",

    "pixel_name": "Main ad account",

    "has_token": true,

    "test_event_code": null,

    "ad_account_id": null,

    "conversion_label": null,

    "is_active": false,

    "is_default": false,

    "created_at": "2026-10-01 14:20:05",

    "updated_at": "2026-10-02 09:05:41"

  }

}
```

## `DELETE /v1/pixels/{id}`

Deletes the pixel and its assignments to products, categories and landing pages.

**Auth:** platform key with `pixels:write`. **Requires `Idempotency-Key`.**

### Request

```
curl -X DELETE 'https://api.dzbuild.app/v1/pixels/12' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Idempotency-Key: pixel-12-delete-1"
```

### Response 200

```
{

  "data": {

    "deleted": true,

    "id": 12

  }

}
```

## Undo

Each pixel write made through the API is recorded in the store's change history. The write answer carries no change id: find it with `GET /v1/changes?entity=pixels`, newest first, which needs `store:read`. `POST /v1/changes/{id}/undo` reverts one change and needs `pixels:write` and an `Idempotency-Key`. See [Changes and undo](https://dzbuild.com/api-docs/resources/changes.md).

* Undoing an update restores `pixel_name`, `ad_account_id`, `conversion_label`, `is_active` and `is_default`. The token is not in the history, so it stays as it is now.
* Undoing a delete adds the pixel back with its old fields, and with its old `id` when that id is still free, but without its access token and without its assignments. The plan limit and the duplicate check still apply, so this undo can answer `409 limit_reached` or `409 pixel_exists`.
* An added pixel cannot be undone: the undo answers `422 nothing_to_restore`. Delete the pixel instead.
* Undoing an update of a pixel deleted since then answers `422 restore_target_missing`.
* Pixel changes saved on the dashboard are not recorded, so they cannot be undone through the API.
* An installed app's token cannot read or undo changes: both answer `403 forbidden`.

## Errors

| HTTP | Code                    | Cause                                                                                                                                                                                 |
| ---- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`           | The body is not valid JSON, the pixel id in the path is not all digits, or `Idempotency-Key` is missing or malformed on `POST`, `PATCH` or `DELETE`.                                  |
| 401  | `unauthorized`          | Bad or missing key.                                                                                                                                                                   |
| 402  | `quota_exceeded`        | The store's monthly request quota is used up. See [Rate limits](https://dzbuild.com/api-docs/rate-limits.md).                                                                         |
| 403  | `forbidden`             | `Missing scope: pixels:read` or `Missing scope: pixels:write`, or `API access requires an active Enterprise plan` for a personal key whose store is not on an active Enterprise plan. |
| 404  | `not_found`             | No pixel with this id in the store. A pixel of another store answers the same way.                                                                                                    |
| 409  | `limit_reached`         | The plan allows no more pixels of this type.                                                                                                                                          |
| 409  | `pixel_exists`          | The store already has a pixel of this type with this `pixel_id`.                                                                                                                      |
| 413  | `payload_too_large`     | The body is over 1 MB.                                                                                                                                                                |
| 422  | `invalid_pixel_type`    | `pixel_type` is missing or not one of the seven types.                                                                                                                                |
| 422  | `invalid_pixel_id`      | `pixel_id` is missing, longer than 100 characters, holds a character other than letters, digits, `-` and `_`, or is a `facebook` id that is not 15 to 17 digits.                      |
| 422  | `invalid_access_token`  | The token breaks one of the access token rules.                                                                                                                                       |
| 422  | `immutable_field`       | A `PATCH` sent `pixel_type`, `type` or `pixel_id`.                                                                                                                                    |
| 422  | `pixel_write_failed`    | The write was refused after the checks above passed. The message gives the reason and can be in Arabic.                                                                               |
| 422  | `idempotency_key_reuse` | The same `Idempotency-Key` was used with a different method, path or body.                                                                                                            |
| 429  | `rate_limited`          | Too many requests. Wait for `Retry-After`.                                                                                                                                            |
| 500  | `server_error`          | The request failed. Retry with the same `Idempotency-Key`.                                                                                                                            |
