# Store

The "store" is the top-level container of products, orders, customers, etc. Every key is bound to **exactly one** store. There is no way to query other merchants' stores.

## `GET /v1/store`

Returns the profile of the store the calling key belongs to.

**Auth:** platform key with `store:read`. Merchant keys carry it by default; a key without it gets `403 forbidden` (`Missing scope: store:read`).

Served fresh on every call: a dashboard change appears on the next `GET` through `api.dzbuild.app` and through the alias `dzbuild.com/api/v1/store`.

### Request

```
curl https://api.dzbuild.app/v1/store \

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

### Response 200

```
{

  "data": {

    "id":         12345,

    "name":       "My Store",

    "slug":       "my-store",

    "language":   "ar",

    "description": "Short tagline",

    "logo":       "/uploads/logos/12345/logo.webp",

    "favicon":    null,

    "banner":     null,

    "theme": {

      "primary_color":    "#f59e0b",

      "secondary_color":  "#fbbf24",

      "background_color": "#ffffff",

      "font_family":      "Cairo"

    },

    "store_theme":          "starter",

    "fast_checkout_theme":  "classic",

    "variant_card_style":   "default",

    "subdomain":            "my-store.dzbuild.app",

    "custom_domain":        null,

    "custom_domain_verified": false,

    "public_url":            "https://my-store.dzbuild.app",

    "hide_branding":         false,

    "created_at":            "2026-01-01 12:00:00"

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

### Field reference

| Field                    | Type         | Notes                                                                                                                                                                                                 |
| ------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                     | int          | Stable internal id. Same as `store_id` everywhere else.                                                                                                                                               |
| `name`                   | string       | Display name. Shown in storefront navbar + emails.                                                                                                                                                    |
| `slug`                   | string       | URL-safe identifier. Used in `<slug>.dzbuild.app` etc.                                                                                                                                                |
| `language`               | enum         | `ar` or `fr`. Drives storefront RTL/LTR.                                                                                                                                                              |
| `description`            | string\|null | Short tagline.                                                                                                                                                                                        |
| `logo`                   | string\|null | Path on `cdn.dzbuild.app` if set. Prepend the CDN base if you display it.                                                                                                                             |
| `favicon`                | string\|null | Same.                                                                                                                                                                                                 |
| `banner`                 | string\|null | Same.                                                                                                                                                                                                 |
| `theme.primary_color`    | hex string   | The dominant button + accent color.                                                                                                                                                                   |
| `theme.secondary_color`  | hex string   | Hover / secondary accents.                                                                                                                                                                            |
| `theme.background_color` | hex string   | Page background.                                                                                                                                                                                      |
| `theme.font_family`      | string       | Typography (default `Cairo`).                                                                                                                                                                         |
| `store_theme`            | string       | Key of the storefront theme in use, the same as `current` in `GET /v1/themes`. Read-only here: switch it with `POST /v1/store/theme`.                                                                 |
| `fast_checkout_theme`    | string       | Key of the fast-checkout form theme, `classic` until the merchant picks another. Switch it with `POST /v1/store/fast-checkout-theme`.                                                                 |
| `variant_card_style`     | string       | Key of the variant picker style, `default` when no style is set. Switch it with `POST /v1/store/variant-style`.                                                                                       |
| `subdomain`              | string\|null | The DZBuild-issued subdomain. Normally present; `null` if the store has no subdomain configured yet.                                                                                                  |
| `custom_domain`          | string\|null | The merchant's own domain. Only set if added via dashboard.                                                                                                                                           |
| `custom_domain_verified` | bool         | `true` once the domain's DNS settings are confirmed. The security certificate and the final check can still be running at that point; `public_url` switches to the domain only when it is fully live. |
| `public_url`             | string\|null | Where customers actually land: the merchant's own domain once it is fully live and set as the store's main address, otherwise the subdomain; `null` if neither exists.                                |
| `hide_branding`          | bool         | "Powered by DZBuild" hidden in storefront footer. Unlimited.                                                                                                                                          |
| `created_at`             | timestamp    | Store creation time, in Algiers time (UTC+01:00).                                                                                                                                                     |

### Errors

| HTTP | Code             | Cause                                                                                                                                                                                                                     |
| ---- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`   | Bad or missing key                                                                                                                                                                                                        |
| 402  | `quota_exceeded` | Store's monthly request quota exhausted — see [Rate limits](https://dzbuild.com/api-docs/rate-limits.md)                                                                                                                  |
| 403  | `forbidden`      | `Missing scope: store:read`; a merchant key whose store is not on an active Enterprise plan ("API access requires an active Enterprise plan"); or pilot mode: key not enrolled ("API is in pilot mode; key not enrolled") |
| 404  | `not_found`      | The store was deleted while you were holding the key (very rare)                                                                                                                                                          |
| 429  | `rate_limited`   | Per-minute cap for this store, shared by all of its keys; honour `Retry-After`. See [Rate limits](https://dzbuild.com/api-docs/rate-limits.md)                                                                            |

## `PATCH /v1/store`

Updates the store's settings profile, the same fields as the store settings page in the dashboard except the WhatsApp number. Only the fields you send change.

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

### Body

| Field                      | Type         | Notes                                                                                    |
| -------------------------- | ------------ | ---------------------------------------------------------------------------------------- |
| `store_name`               | string       | Up to 100 characters. Cannot be empty.                                                   |
| `description`              | string       | Up to 5000 characters.                                                                   |
| `wilaya_id`                | int\|null    | A wilaya id from `GET /v1/wilayas` (needs `shipping:read`). `0` or `null` clears it.     |
| `commune`                  | string       | Up to 100 characters.                                                                    |
| `address`                  | string       | Up to 500 characters.                                                                    |
| `store_phone`              | string       | Up to 20 characters.                                                                     |
| `store_email`              | string\|null | A valid email address. `""` or `null` clears it.                                         |
| `google_site_verification` | string\|null | Google Search Console verification code, up to 100 characters. `""` or `null` clears it. |
| `bing_site_verification`   | string\|null | Bing Webmaster verification code, up to 100 characters. `""` or `null` clears it.        |

HTML tags are removed from text values and longer text is cut to the limit. The slug, the custom domain and the colors are not part of this endpoint; colors and other design values are changed with `PATCH /v1/store/design`.

### Request

```
curl -X PATCH https://api.dzbuild.app/v1/store \

  -H "Authorization: Bearer $DZ_KEY" \

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

  -H "Idempotency-Key: store-profile-1" \

  -d '{"store_phone": "0550000000", "description": "Short tagline"}'
```

### Response 200

```
{

  "data": {

    "updated": ["description", "store_phone"],

    "values":  {"description": "Short tagline", "store_phone": "0550000000"}

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

The change is recorded and can be undone with `POST /v1/changes/{change_id}/undo`; find its id with `GET /v1/changes?entity=store.settings`. A `GET /v1/store` sent right after the write returns the new values.

### Errors

| HTTP | Code                    | Cause                                                                                            |
| ---- | ----------------------- | ------------------------------------------------------------------------------------------------ |
| 400  | `bad_request`           | The body is not valid JSON, or `Idempotency-Key` is missing or malformed.                        |
| 403  | `forbidden`             | `Missing scope: store:write`, or a merchant key whose store is not on an active Enterprise plan. |
| 404  | `store_not_found`       | The store was deleted.                                                                           |
| 422  | `no_writable_fields`    | The body holds none of the fields above.                                                         |
| 422  | `invalid_value`         | A field is an object or a list, or `store_name` is empty.                                        |
| 422  | `invalid_wilaya`        | `wilaya_id` is not a known wilaya.                                                               |
| 422  | `invalid_email`         | `store_email` is not a valid email address.                                                      |
| 422  | `idempotency_key_reuse` | The same `Idempotency-Key` was used with another body.                                           |

## `GET /v1/store/design`

Returns the store's design: the fields of the customize page of the dashboard (`/dashboard/customize`) for the store's current theme, grouped in the same sections, each with its current value. A few themes hide some of these fields on that page; the API lists them all.

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

Served fresh through `api.dzbuild.app`, like `GET /v1/store`. The answer of `PATCH /v1/store/design` also carries the written values in `values`.

### Request

```
curl https://api.dzbuild.app/v1/store/design \

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

### Response 200

Two sections are shown, with some of their fields.

```
{

  "data": {

    "theme": "starter",

    "plan":  "enterprise",

    "sections": [

      {

        "key":  "theme",

        "name": {"ar": "الألوان والخط", "fr": "Couleurs et Police"},

        "plan_required": "free",

        "page": "home",

        "fields": [

          {"key": "primary_color", "type": "color", "plan_required": "free", "writable": true, "locked": false, "value": "#f59e0b"},

          {"key": "background_color", "type": "color", "plan_required": "pro", "writable": true, "locked": false, "value": "#ffffff"},

          {"key": "font_family", "type": "select", "plan_required": "free", "writable": true, "locked": false, "value": "Cairo"}

        ]

      },

      {

        "key":  "productCard",

        "name": {"ar": "بطاقة المنتج", "fr": "Carte produit"},

        "plan_required": "free",

        "page": "home",

        "fields": [

          {"key": "card_hide_price", "type": "switch", "plan_required": "free", "writable": true, "locked": false, "value": false},

          {"key": "card_border_radius", "type": "select", "plan_required": "free", "writable": true, "locked": false, "allowed_values": ["0px", "8px", "16px", "24px"], "value": "16px"}

        ]

      }

    ]

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

### Field reference

| Field                      | Type   | Notes                                                                                                                                                              |
| -------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `theme`                    | string | Key of the theme the store uses. See [Themes](https://dzbuild.com/api-docs/resources/themes.md).                                                                   |
| `plan`                     | string | The plan the locks are worked out for: `free`, `pro`, `unlimited` or `enterprise`. A paid plan that has expired counts as `free`.                                  |
| `sections[].key`           | string | Section id, for example `header`, `theme`, `productCard` or `checkoutPage`.                                                                                        |
| `sections[].name`          | object | The section title the dashboard shows, in `ar` and `fr`.                                                                                                           |
| `sections[].plan_required` | string | The plan the dashboard shows on the section.                                                                                                                       |
| `sections[].page`          | string | `home` when the customize page lists the section under the home page preview, `all` when it lists it on every preview.                                             |
| `sections[].fields`        | array  | The section's fields. Can be empty: a few sections, such as `helpWidget` and the home page blocks of the Digital theme, are not saved through design fields.       |
| `fields[].key`             | string | The name to send in `PATCH /v1/store/design`.                                                                                                                      |
| `fields[].type`            | string | The dashboard control: `text`, `textarea`, `color`, `select` or `switch`.                                                                                          |
| `fields[].plan_required`   | string | The plan the dashboard shows on the field.                                                                                                                         |
| `fields[].writable`        | bool   | `false` when the API does not accept the field. `custom_js` is one: it is set in the dashboard only.                                                               |
| `fields[].locked`          | bool   | `true` when the store's plan cannot write the field through the API. Read this flag, not `plan_required`, to know whether a write will be applied.                 |
| `fields[].allowed_values`  | array  | Only on fixed-choice fields: the values the field takes.                                                                                                           |
| `fields[].value`           | any    | The current value. Switches come back as `true` or `false`, `products_per_page` as a number, the rest as stored. `null` when the store has no value for the field. |

## `GET /v1/store/design/fields`

The same list without the `value` key. Call it before a write to see which fields the store's theme shows, the name to send for each one and which ones the plan locks.

**Auth:** platform key with `store:read`. Served fresh through `api.dzbuild.app`, like `GET /v1/store`.

### Request

```
curl https://api.dzbuild.app/v1/store/design/fields \

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

### Response 200

The last two sections are shown.

```
{

  "data": {

    "theme": "starter",

    "plan":  "enterprise",

    "sections": [

      {

        "key":  "customCss",

        "name": {"ar": "CSS مخصّص", "fr": "CSS personnalisé"},

        "plan_required": "enterprise",

        "page": "all",

        "fields": [

          {"key": "custom_css", "type": "textarea", "plan_required": "enterprise", "writable": true, "locked": false}

        ]

      },

      {

        "key":  "customJs",

        "name": {"ar": "JavaScript مخصّص", "fr": "JavaScript personnalisé"},

        "plan_required": "enterprise",

        "page": "all",

        "fields": [

          {"key": "custom_js", "type": "textarea", "plan_required": "enterprise", "writable": false, "locked": false}

        ]

      }

    ]

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

### Errors

`GET /v1/store/design` and `GET /v1/store/design/fields` answer the same errors as `GET /v1/store`: `401 unauthorized`, `402 quota_exceeded`, `403 forbidden` (`Missing scope: store:read`, plan or pilot), `404 not_found` and `429 rate_limited`.

## `PATCH /v1/store/design`

Changes design fields: colours, header, hero, product cards, product page, checkout texts, footer, social links, SEO and more. Only the fields you send change. Colours and `hide_branding` are set here, not with `PATCH /v1/store`.

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

### Body

Send any field that `GET /v1/store/design/fields` lists with `writable: true`. The design fields the current theme does not list are accepted too: they are stored, and a theme that shows them uses them. Keys the API does not know are ignored. Each value is cleaned by its kind:

| Kind            | Examples                                                            | What is stored                                                                                                                                                                       |
| --------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Switch          | `show_hero`, `navbar_sticky`, `card_hide_price`                     | Send `true` or `false`. The answer shows `1` or `0`.                                                                                                                                 |
| Colour          | `primary_color`, `navbar_color`, `footer_color`                     | A `#RRGGBB` value. `null` clears it. Any other value is replaced with the field's default colour, for example `#f59e0b` for `primary_color`.                                         |
| Override colour | `announcement_bg_color`, `product_buy_now_color`, `fc_button_color` | A `#RRGGBB` value. Anything else clears the override.                                                                                                                                |
| Text            | `hero_title`, `footer_about`, `seo_description`                     | HTML tags are removed and the text is cut to the field's length, for example 255 characters for `hero_title` and 2000 for `footer_about`.                                            |
| Link            | `hero_button_link`, `facebook`, `custom_link_url`                   | An `https://`, `http://`, `mailto:` or `tel:` link, a `/path`, an `#anchor` or a bare handle, cut to 255 characters (20 for `whatsapp`). Any other scheme answers `422 invalid_url`. |
| Fixed choice    | `card_border_radius`, `checkout_layout`, `store_language`           | One of the field's `allowed_values`. Any other value is stored as the field's default.                                                                                               |
| Number          | `products_per_page`                                                 | Kept between 4 and 48.                                                                                                                                                               |

Some fields have their own rules:

* `font_family`: Latin letters, digits, spaces and hyphens. Anything else is stored as `Cairo`. The dashboard offers `Cairo`, `Tajawal` and `Almarai`.
* `button_style`: the dashboard offers `rounded`, `square` and `pill`.
* `navbar_style` takes `default`, `centered`, `minimal` or `transparent`; `navbar_menu_style` takes `default`, `pills`, `underline` or `buttons`; `navbar_logo_size` takes `small`, `medium` or `large`; `cart_icon_style` takes `default`, `filled`, `outline` or `minimal`. These four carry no `allowed_values`, and any other value refuses the whole write with `422 invalid_value`.
* `facebook_pixel_id`: 15 to 17 digits, or empty to clear it.
* `custom_css`: Enterprise plan only. It is cleaned before it is stored.

The buy bar and the order form on product pages have four fields of their own. Every plan can write them, and their defaults keep the look a store had before they existed.

| Field                 | Values                        | What it changes                                                                                                                                                                            |
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `buybar_show_mobile`  | `true` (default) or `false`   | `false` hides the buy bar fixed at the bottom of product pages on phones. The bar stays when the fast-checkout form is off, so a phone always shows a buy button.                          |
| `buybar_show_qty`     | `true` (default) or `false`   | `false` removes the quantity buttons from the buy bar, on phones and computers.                                                                                                            |
| `fc_show_qty`         | `true` (default) or `false`   | `false` removes the quantity from the fast-checkout form. The buyer then orders one item, unless they picked an offer or a quantity in the buy bar, or the product has a minimum quantity. |
| `product_button_size` | `normal` (default) or `large` | `large` makes the buy bar buttons and the order button of the fast-checkout form 56 pixels tall, with a 17 pixel label. Any other value is stored as `normal`.                             |

The Digital theme has no fast-checkout form: there `fc_show_qty` changes nothing, and `buybar_show_mobile: false` hides the phone bar while the buy buttons stay on the page.

### Plan locks

A field the store's plan locks is not written. It is listed in `skipped` with the reason, and the call still answers `200` when another field was written. When every field you sent is skipped, the call answers `422 no_writable_fields`.

| Reason in `skipped`            | When                                                                                                                                                                    |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `requires a paid plan`         | A field reserved for the `pro` plan and above, for example `background_color`, `navbar_color`, `hide_branding` or the announcement bar fields, sent for a `free` store. |
| `requires the enterprise plan` | `custom_css` on any plan other than `enterprise`.                                                                                                                       |
| `requires the unlimited plan`  | `hide_branding: true` on a `pro` store. A `pro` store can still send `hide_branding: false`, which shows "Powered by DZBuild" in the footer again.                      |

A merchant key belongs to a store on an active Enterprise plan, so nothing is locked for it. The locks apply to installed-app tokens, which work on every plan.

### Request

```
curl -X PATCH https://api.dzbuild.app/v1/store/design \

  -H "Authorization: Bearer $DZ_KEY" \

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

  -H "Idempotency-Key: store-design-1" \

  -d '{"primary_color": "#0f766e", "show_hero": true, "hero_title": "New collection"}'
```

### Response 200

```
{

  "data": {

    "updated": ["primary_color", "show_hero", "hero_title"],

    "skipped": [],

    "values":  {"primary_color": "#0f766e", "show_hero": 1, "hero_title": "New collection"},

    "change_id": 812

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

`updated` lists the fields written and `values` the value stored for each, after cleaning. `skipped` is an empty list when nothing was skipped, and otherwise an object such as `{"hide_branding": "requires the unlimited plan"}`.

The change is recorded and can be undone with `POST /v1/changes/{change_id}/undo`, using the `change_id` of the answer; it is `null` in the rare case the write was saved without an undo record, and `GET /v1/changes?entity=store.design` lists the earlier ones. An installed app's token cannot read or undo changes (`403 forbidden`, `Apps cannot use this endpoint`). A `GET /v1/store/design` sent right after the write returns the new values.

### Errors

| HTTP | Code                    | Cause                                                                                              |
| ---- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| 400  | `bad_request`           | The body is not valid JSON, or `Idempotency-Key` is missing or malformed.                          |
| 403  | `forbidden`             | `Missing scope: store:write`, or a merchant key whose store is not on an active Enterprise plan.   |
| 404  | `store_not_found`       | The store was deleted.                                                                             |
| 413  | `payload_too_large`     | The body is larger than 1 MB.                                                                      |
| 422  | `no_writable_fields`    | The body holds no design field, or every field it holds was skipped for the plan.                  |
| 422  | `invalid_value`         | A field is an object or a list, or a value was refused, such as a `navbar_style` outside its list. |
| 422  | `invalid_url`           | A link field uses a scheme other than `https`, `http`, `mailto` or `tel`.                          |
| 422  | `invalid_pixel_id`      | `facebook_pixel_id` is not 15 to 17 digits.                                                        |
| 422  | `idempotency_key_reuse` | The same `Idempotency-Key` was used with another body.                                             |

A `422` writes nothing.
