# Core concepts

## Apps[​](#apps "Direct link to Apps")

An app is the record you create in the developer console. It holds your credentials, your redirect URIs, the scopes you may request, your webhook settings and a minimum plan. Each app has one status:

| Status      | Who can install it                                       | What happens to existing installs                                                                                                  |
| ----------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `draft`     | Only the developer, on stores the developer account owns | Tokens work on the developer's own stores                                                                                          |
| `in_review` | Only the developer, on stores the developer account owns | Tokens work on the developer's own stores. An approved app sent back to review after an edit keeps every existing install working. |
| `approved`  | Any store owner                                          | Tokens work                                                                                                                        |
| `rejected`  | Nobody                                                   | Every call answers `403` with `app_suspended`                                                                                      |
| `suspended` | Nobody                                                   | Every call answers `403` with `app_suspended`                                                                                      |

DZBuild reviews an app after you submit it from the console. The [review guidelines](https://dzbuild.dev/review-guidelines.md) list what the review checks.

## Test mode[​](#test-mode "Direct link to Test mode")

A draft or in-review app runs in test mode. The consent screen accepts it only for the developer who owns it, and only for stores that account owns. This lets you build and test the whole flow before review, with real data from your own store.

The API applies the same rule on every call. If an app has not been approved yet and the install belongs to someone other than the developer, the call answers `403` with `app_not_approved` and the message `This app is in test mode and only runs on its developer's stores`.

## Stores[​](#stores "Direct link to Stores")

A store is one DZBuild shop, with its own products, orders and plan. One merchant account can own several stores.

* Only the store owner can install an app. The consent screen lists only the stores the logged-in account owns, so a team member cannot pick a store they work on.
* One consent can cover up to 10 stores. Each store gets its own install and its own token.
* A token reads and writes only the store it was issued for. The token response lists every installed store in `stores`, and repeats the first one at the top level.

## Installs[​](#installs "Direct link to Installs")

An install links one app to one store. There is at most one install per app and store.

The merchant starts an install from the Extensions page of their dashboard. The Install button opens your homepage URL, or your launch URL when no homepage is set, in a new tab. DZBuild does not send the merchant to the authorize URL for you, so that page must start the OAuth flow.

* The install is created when your server exchanges the code at the token endpoint, not when the merchant clicks approve.
* If the merchant installs your app again on the same store, the install keeps its `install_id`. DZBuild issues a new token, revokes the old one and stores the scopes from the latest consent.
* Only the merchant can uninstall, from your app's page in their dashboard. There is no revoke endpoint for apps.

When a merchant uninstalls your app, DZBuild does four things at once: it marks the install as uninstalled, revokes the token, drops the webhook deliveries still waiting for your endpoint, and queues one `app.uninstalled` event if your endpoint is verified and active. From then on the old token answers `401`.

## Plans and min_plan[​](#plans-and-min_plan "Direct link to Plans and min_plan")

DZBuild stores are on one of four plans, from lowest to highest: `free`, `pro`, `unlimited`, `enterprise`. Any plan can install an app. The Enterprise plan requirement for merchant API keys does not apply to install tokens.

You can raise the bar with `min_plan` in the console. It defaults to `free`, which allows every store.

* On the consent screen, a store below `min_plan` is shown as not eligible and cannot be picked.
* On every API call, a store below `min_plan` answers `403` with `app_plan_required` and a message that names the plan, for example `This app requires the Pro plan`.
* A paid plan that has expired counts as `free` in both checks.

## Per-install rate limit[​](#per-install-rate-limit "Direct link to Per-install rate limit")

Each install has its own budget of 120 requests per minute. The window is a fixed calendar minute, so the counter resets at the start of each minute.

The install budget is checked first. After it, the request also counts against the store's shared per-minute budget, which the merchant's own keys and other apps use too. A runaway app hits its own limit before it can use up the store's.

Over the limit, the API answers `429` with a `Retry-After` header and this body:

```
{

  "error": {

    "code": "rate_limited",

    "message": "Per-minute API limit exceeded for this app install",

    "retry_after": 42

  },

  "meta": {

    "request_id": "5f2c9a0b1d3e4f60",

    "api_version": "v1"

  }

}
```

If the store budget runs out first, the message is `Per-minute API limit exceeded for this store`. [Rate limits](https://dzbuild.dev/rate-limits.md) explains how to back off.

## Install tokens[​](#install-tokens "Direct link to Install tokens")

An install token is the bearer token your server receives from the token endpoint. It is an API key of the same kind a merchant creates, marked as belonging to your install.

What an install token is:

* A bearer token that starts with `dzpk_live_`, sent as `Authorization: Bearer ...`.
* One token per install, so one per store.
* Limited to the scopes the merchant approved, checked by the same scope rules as every other key.
* Reported as `rate_limit_tier: enterprise` by `GET /v1/whoami` on any plan, with the per-install limit above as the real budget.
* Valid with no expiry date. The token endpoint returns no `expires_in` and no refresh token.
* Revoked when the merchant uninstalls your app, and replaced when the merchant installs it again.

What an install token is not:

* It is not a merchant key. It does not appear on the merchant's API keys page and does not count toward the merchant's key limit.
* It cannot manage keys, webhooks or the change log. Calls to `/v1/keys`, `/v1/webhooks` and `/v1/changes` answer `403` with the code `forbidden` and the message `Apps cannot use this endpoint`.
* It is not tied to a person's session. It keeps working when the merchant logs out.
* It does not cover several stores. Use the token issued for each store.

Every refusal that is specific to apps answers `403` with the error envelope:

| Code                | Message                                                            | Cause                                                                      |
| ------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `app_uninstalled`   | `This app is no longer installed on this store`                    | The install is no longer active                                            |
| `app_suspended`     | `This app has been suspended by DZBuild`                           | The app is suspended or rejected                                           |
| `app_not_approved`  | `This app is in test mode and only runs on its developer's stores` | The app has not been approved yet and the store owner is not the developer |
| `app_plan_required` | `This app requires the Pro plan` (the plan name varies)            | The store is below `min_plan`                                              |
