Exchange is excluded from this API/CLI release. Any historical Exchange API or CLI example in this guide is unavailable; native portal behavior is outside this exclusion. See [current release scope](/developers/guides/release-scope?lang=en).

# Review default margins and catalogue prices

Portal, API and CLI use the same pricing policy for **Domains, Exchange, Spam, Hosting and SSL**. A current internal human administrator with recent passkey verification is required. A service connection cannot change this policy. The API requires `admin:read` or `admin:write`; the selected administration context is the administrator's own approved management company.

This action changes only the default policy and explicitly selected future catalogue prices. Existing subscriptions, invoices and separate reseller prices are preserved. Nothing is changed at a supplier. Hosting package administration retains its own API; SSL gains only a pricing policy through this feature, not a new product or order API.

## Choose the impact

| `apply_to` | Impact |
| --- | --- |
| `none` | Save only the new default margin and any policy options |
| `following` | Only products that follow both the server-read old default margin and the calculated old sale price |
| `all` | Recalculate all non-fixed catalogue prices, including manual deviations |

A fixed-price product (`fixedPrice:Y`) is preserved in all five modules, including with `all`. With `following`, deviating margins and manually set sale prices are preserved. The client never supplies a trusted old margin: the server reads and locks the current policy and products itself.

The compatibility option `apply_to_all:false` means `none`; `true` means `all`. Never combine this option with `apply_to`. Without either option, only the policy changes. The native page always requires an explicit choice.

## Portal

Open `/products/pricing-policy/domains` (or `exchange`, `spam`, `hosting`, `ssl`) using the margin button in the module. Read the current policy first and verify your passkey. Choose the margin, impact and rounding, then use **Review impact first**. Check the total product count, the products to change, and the fixed and manual prices to preserve. Then explicitly confirm the change yourself.

Changed input invalidates the review. Expired verification requires a new review. An uncertain response blocks automatic resaving; reload and read the current policy before approving a separate new change. The old versionless `currentDefaultMargin`/`newDefaultMargin` POST is refused.

## API

Paths start with `/api/v1/accounts/{administratiecontext}`:

- `GET /admin/{area}/pricing-policy` reads the current policy, `product_count`, `version` and the same ETag.
- `PUT /admin/{area}/pricing-policy` requires the current `If-Match` and an explicit JSON body. An actual change also requires a durable `Idempotency-Key`; a dry-run creates no intent key.

```json
{"margin":"25.0000","apply_to":"following","rounding":"cents"}
```

`margin` is non-negative and has at most four decimal places, up to `9999.9999`. `rounding` is `none`, `cents`, `whole` or `charm`; the saved catalogue sale price always has two decimal places. The calculation uses fixed integer units for four cost and margin decimal places, without binary floating-point arithmetic.

The optional booleans `auto_update_sell_price`, `auto_create_products` and `auto_enable_products` change only the existing saved synchronisation preferences. Omitted options are preserved. This request does not itself run a supplier synchronisation.

`dry_run:true` checks the same current authority and version, but saves no change, audit, operation or supplier action. `data.plan.reviewed_version` is the originally reviewed version; `product_count` is the total number of products. `updated_products`, `fixed_products_preserved` and `manual_products_preserved` describe the impact. The actual response contains the same review binding plus the new current `version`.

A changed version returns 412; a missing version 428. On a version conflict, read again and review the new counts. Do not create a new idempotency key after an uncertain response to blindly repeat the same original change. Investigate the current policy and original operation.

## CLI and AI agents

```sh
binadit admin hosting pricing-policy get --account 1
binadit admin hosting pricing-policy update --account 1 --version '<version uit get>' \
  --set margin=25 --set apply_to=following --dry-run
binadit admin hosting pricing-policy update --account 1 --version '<dezelfde beoordeelde version>' \
  --set margin=25 --set apply_to=following --yes
```

Replace `hosting` with `domains`, `exchange`, `spam` or `ssl`. A JSON file can be supplied through `--from-file beleid.json` or stdin (`-`). The CLI shows only the fixed policy and validated counts; unknown supplier fields are not printed. An uncertain or contradictory response retains the local original intent key.

An agent shows the module, old and new margin, impact, rounding and counts before confirmation. `all` is an explicit choice to replace manual catalogue prices and must not be inferred from `following`. `--yes` replaces only the local interactive confirmation, never server permissions, passkey verification or a current version.

## Limits and concurrency

A review supports at most 5,000 products per module. Product metadata is limited to 64 KiB per details/description field and 8 MiB in total. A larger or corrupted catalogue requires explicit investigation; no non-existent batch route is promised. Current production has 2,060 domain products and fits within this limit.

Current SQL locks protect the same policy row and product selection that make up the version. An ongoing Hosting package revision blocks price recalculation until it is completed or reconciled. The final authority and clock check follows all price, audit and operation updates: an expired approval does not publish a partial change.
