Skip to main content

Changes and undo

List the configuration changes made through the API, read one with the values it replaced, and undo it to put those values back on the store.

Written by Support

Most configuration writes made through the API are recorded as changes: store settings, design and theme, home page sections, categories, stock, promo codes, pixels, shipping rates and settings, and landing page sections. Each change keeps the values it replaced, and an undo writes them back through the same checks as the original write. Writes made on the store by Copilot, a connected AI assistant (Claude or ChatGPT) or an installed app are recorded too. Changes saved in the dashboard are not.

The three endpoints below list the changes, read one in full and undo one. The writes under /v1/store/home-layout and the courier rate sync answer with a change_id. For the other writes, find the change with GET /v1/changes.

What is recorded

entity

Recorded by

entity_id

Scope to read it in full

Scope to undo it

store.settings

PATCH /v1/store

settings

store:read

store:write

store.design

PATCH /v1/store/design

design

store:read

store:write

store.theme

POST /v1/store/theme, POST /v1/store/fast-checkout-theme, POST /v1/store/variant-style

theme

store:read

store:write

store.home_sections

PATCH /v1/store/home-sections

home_sections

store:read

store:write

store.home_layout

Every write under /v1/store/home-layout

layout

store:read

store:write

category

POST /v1/categories, PATCH and DELETE /v1/categories/{id}

Category id

products:read

products:write

stock

POST /v1/products/{id}/stock

Product id

products:read

products:write

promo_code

POST /v1/promo-codes, PATCH and DELETE /v1/promo-codes/{id}

Promo code id

promos:read

promos:write

pixels

POST /v1/pixels, PATCH and DELETE /v1/pixels/{id}

The pixel's id on DZBuild, not its pixel_id

pixels:read

pixels:write

shipping.rates

POST /v1/shipping/rates, POST /v1/shipping/rates/sync

rates

shipping:read

shipping:write

shipping.settings

PATCH /v1/shipping/settings

settings

shipping:read

shipping:write

lp.section

The section writes under /v1/landing-pages/{id}/sections

Section id, or lp: and the page id for a reorder

landing_pages:read

landing_pages:write

  • These writes are not recorded and cannot be undone: products with their images, variants, offers, add-ons and quantity rules (stock is recorded), orders, landing pages themselves, the category order, couriers, webhooks and keys.

  • lp.page is accepted as an entity filter, but no write records it.

  • Recording does not block a write. When a change cannot be recorded, for example because its before or after values pass 256 KB, the write still goes through and cannot be undone. Shipping rate writes are the exception: they check the size first and answer 422 snapshot_too_large instead of running.

  • Every scope in the table is among the default scopes of a new key, so a key created from the dashboard (Settings → API, /dashboard/api) can use the three endpoints. Scopes are frozen when a key is created, so an older key that lacks the scope it needs answers 403 forbidden: create a new key from the dashboard.

GET /v1/changes

The store's changes, newest first, without the values they replaced.

Auth: platform key with store:read. An installed app's token gets 403 forbidden (Apps cannot use this endpoint).

Query parameters

Param

Type

Default

Notes

entity

string

none

Only the changes of one entity from the table above. Any other value is ignored and the whole list comes back.

limit

int

25

1 to 100. A smaller value counts as 1, a larger one as 100.

cursor

string

none

next_cursor of the previous page. See Pagination.

Request

curl 'https://api.dzbuild.app/v1/changes?limit=2' \
  -H "Authorization: Bearer $DZ_KEY"

Response 200

{
  "data": {
    "items": [
      {
        "id": 120,
        "entity": "shipping.rates",
        "entity_id": "rates",
        "action": "update",
        "summary": "Shipping rates updated for 2 wilaya(s)",
        "undone_at": null,
        "created_at": "2026-10-06 11:02:17",
        "undone": false
      },
      {
        "id": 119,
        "entity": "promo_code",
        "entity_id": "7",
        "action": "update",
        "summary": "Updated promo code SUMMER10",
        "undone_at": null,
        "created_at": "2026-10-06 10:52:30",
        "undone": false
      }
    ],
    "next_cursor": "MTE5",
    "has_more": true
  }
}

Field

Meaning

id

The change id, for GET /v1/changes/{id} and the undo.

entity

What was changed. See the table above.

entity_id

Which item was changed, as a string. See the table above.

action

create, update or delete. An undo is recorded as an update.

summary

A short description in English. An undo reads Undo of change # followed by the id it undid.

undone

true once the change has been undone.

undone_at

When the change was undone, otherwise null.

created_at

YYYY-MM-DD HH:MM:SS, server time. undone_at uses the same format.

GET /v1/changes/{id}

One change with before, the values it replaced, and after, the values it wrote. Their shape depends on the entity: some keep only the fields the write touched, others the whole item.

Auth: platform key with store:read, plus the read scope of the change's entity from the table above. An installed app's token gets 403 forbidden.

Request

curl 'https://api.dzbuild.app/v1/changes/118' \
  -H "Authorization: Bearer $DZ_KEY"

Response 200

{
  "data": {
    "id": 118,
    "key_id": "dzpk_live_xxxxxxxxxxxxxx",
    "entity": "category",
    "entity_id": "12",
    "action": "update",
    "summary": "Updated category #12 (name, slug)",
    "undone_at": null,
    "undone_by_id": null,
    "created_at": "2026-10-06 10:41:05",
    "before": {"name": "Shoes", "slug": "shoes"},
    "after": {"name": "Sneakers", "slug": "sneakers"},
    "undone": false
  }
}

The answer carries the fields of the list, plus these.

Field

Meaning

key_id

The key that made the change, or null for an undo made from the dashboard.

undone_by_id

The id of the change that undid this one, otherwise null.

before

The values the change replaced. null for a create.

after

The values the change wrote. null for a delete and for a courier rate sync.

A pixel's access token is never kept in a change. When the pixel has one, before and after show •••••••• in its place.

Errors

HTTP

Code

Cause

400

bad_request

The id in the path is not all digits.

403

forbidden

The key lacks store:read or the read scope of the change's entity (Missing scope: ...), or the call comes from an installed app's token.

404

not_found

No change with this id in the store.

POST /v1/changes/{id}/undo

Writes the change's before values back through the same checks as the original write, then records the undo as a new change. No request body.

Auth: platform key with the undo scope of the change's entity from the table above; store:read is not needed. Requires Idempotency-Key. An installed app's token gets 403 forbidden (Apps cannot use this endpoint). The store owner sees the last 20 changes of each installed app on that app's page in the dashboard (/dashboard/apps/{id}, in the list of its latest changes) and can undo them there with the undo button.

What the undo does

  1. A change that created something has nothing to restore and answers 422 nothing_to_restore: delete the item instead. Adding a home page section through /v1/store/home-layout is the exception, because every home layout write is recorded as an update of the whole layout.

  2. An update is undone by writing the before values back over what is there now. Only the home page checks for later changes and answers 409 layout_changed (see Home page sections). To walk back several changes to one item, undo them newest first.

  3. A deleted category comes back with its id, without its image. A deleted promo code comes back with its id when no other code has taken it, and with its usage count.

  4. A deleted pixel comes back with its id when it is still free, but without its access token and without its product, category and landing page assignments. Undoing a pixel update leaves its current access token in place.

  5. A deleted landing page section comes back with a new id.

  6. A stock undo sets each value back to the number recorded before the change, whatever orders did to the stock since.

  7. Undoing POST /v1/shipping/rates puts the prior prices back, and removes the rate of a wilaya that had none before the change. Undoing a courier rate sync puts back every price the sync overwrote, and keeps the rates it added for wilayas that had none.

  8. The undo is recorded as a change of its own, undo_change_id, which you can undo to apply the original change again. When the original change has no after (a delete or a courier rate sync), undoing the undo answers 422 nothing_to_restore.

  9. A change can be undone once. A later undo answers 409 already_undone, and of two undos sent at the same moment only one runs.

  10. When the restore itself fails (restore_target_missing, a refused value or a 500), the change is not marked as undone, so you can retry once the cause is fixed. The retry rules are below.

The undo answer does not carry the restored values. Read the item again: every GET through api.dzbuild.app is served fresh, so the read right after the undo returns the restored values.

Request

curl -X POST 'https://api.dzbuild.app/v1/changes/118/undo' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: undo-118"

Response 200

{
  "data": {
    "undone": true,
    "change_id": 118,
    "entity": "category",
    "undo_change_id": 121
  }
}

Field

Meaning

undone

Always true on a 200.

change_id

The change that was undone.

entity

Its entity.

undo_change_id

The change that records this undo, or null when it could not be recorded.

Errors

HTTP

Code

Cause

400

bad_request

The id in the path is not all digits, or Idempotency-Key is missing or malformed.

403

forbidden

The key lacks the undo scope of the change's entity (Missing scope: ...), or the call comes from an installed app's token.

404

not_found

No change with this id in the store.

409

already_undone

The change was already undone.

409

layout_changed

Home page only: the layout changed after this change.

422

not_undoable

This kind of change cannot be undone.

422

nothing_to_restore

The change created something, or holds no values to restore. Delete the item instead.

422

restore_target_missing

What the change touched no longer exists, for example a category or a landing page section deleted since.

422

idempotency_key_reuse

The same Idempotency-Key was already used for a different request, such as the undo of another change.

4xx

The original write's code

The checks of the original write refuse the values, for example invalid_wilaya on shipping rates, or a theme that is no longer offered.

500

server_error

The undo could not run. The change stays undoable.

Retries and Idempotency-Key

The first answer to a key is stored for 24 hours, a 4xx included. A retry with the same key for the same change gets that answer back with Idempotency-Replay: 1, and no second undo runs. So after you fix the cause of an error, retry with a new key. A 5xx or 429 answer is never stored, so retry it with the same key. See Idempotency.

Did this answer your question?