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).

# Registrar administration, nameservers and existing registrations

The inventory, import and synchronization functions below read the portal's existing registrar integration and link confirmed registrar registrations to local domains. They purchase no registration, transfer or subscription and create no invoice. Only a personal connection of a current internal administrator with recent passkey verification and explicit `admin:read`/`admin:write` rights has access. `--account` remains the approved administration company; the target company uses a separate modern account number.

## Read inventory

```sh
binadit admin domains registrar list --account 1 --limit 100 --output json
binadit admin domains registrar get 501 --account 1 --output json
```

The corresponding routes are `GET /api/v1/accounts/{context}/admin/domains/registrar/portfolio?limit=100&cursor=0` and `GET .../portfolio/{provider_id}`. A page contains at most 100 registrations; `meta.next_cursor` is the next offset. Use `--cursor` for another page or the existing bounded `--all` option. The detail response provides `version`, `local_version`, `owner_account_id` and any `owner_company_name`. The ETag confirms the same registrar version. Registrar passwords, transfer codes and full personal details are not returned.

`owner_requires_reconciliation: true` means the existing company binding is unknown, duplicated, blocked or archived. Repair that binding first; import does not turn it into an arbitrary company. A missing owner remains unbound during global synchronization with legacy value `0`, never automatically customer number `1` or a human user's number.

## Review and execute import

Read each registration first. This example creates the input file from the actual detail response; replace the example numbers with the intended registrar object, administration company and target company.

```sh
umask 077
binadit admin domains registrar get 501 --account 1 --output json > registrar-review.json
jq --argjson target 99 '.data | {domains:[({provider_id,version,local_version} + (if .owner_account_id != null and .owner_account_id != $target then {confirm_owner_account_id:.owner_account_id} else {} end))]}' registrar-review.json > registrar-import.json
binadit admin domains registrar import --from-file registrar-import.json \
  --target-account 99 --confirm-target-account 99 --account 1 --dry-run
binadit admin domains registrar import --from-file registrar-import.json \
  --target-account 99 --confirm-target-account 99 --account 1 --yes
```

Review the selected owner and file contents before the final command. The file contains only `domains`, with one to 25 unique `{provider_id,version,local_version,confirm_owner_account_id?}` entries. Unix files must be private (`0600`); `--from-file -` reads stdin. Fields are not supplied through individual command-line arguments.

The target company applies to new or unbound local registrations. An existing DNS owner remains the owner. If that is another company, every affected entry requires the exact `confirm_owner_account_id` from inspection. This confirms preservation, not transfer. The server compares both owner and local version again under the same write lock. The browser displays the existing company with domain selection; explicit import confirmation and the existing compliant passkey/authenticator browser login preserve this selection. The API and CLI retain their separate recent administrator-verification requirements.

The API uses `POST /api/v1/accounts/{context}/admin/domains/registrar/import`. The body adds `target_account_id` and `confirm_target_account_id` to the same reviewed `domains` list. You can create a valid request file from the file above:

```sh
jq --argjson target 99 '. + {target_account_id:$target,confirm_target_account_id:$target}' registrar-import.json > registrar-api-request.json
binadit api /admin/domains/registrar/import --method POST --from-file registrar-api-request.json --account 1 --yes
```

Versions are the actual 64 hexadecimal characters from GET. `local_version` may be omitted or `null` only if no local DNS row exists yet. For a direct API POST, send one durable `Idempotency-Key`; `X-Binadit-Dry-Run: true` or `dry_run: true` validates without import or an operation record. The CLI handles the key. An exact replay returns the original result even when import itself changed the local version.

The result reports `imported`, `linked` or `skipped`, the local domain number and actual `owner_account_id` for each domain. DNS records, an existing DNS provider route, ongoing billing agreements and existing commercial package choices are preserved. Contacts are linked only within the actual domain owner's company. A foreign or unknown contact grants no access to another company's data.

## Refusal and recovery

A changed registrar or local version returns `412`: inspect the same registration again and review the new details. An original domain order, ambiguous company binding or different registrar identity returns `409`; investigate the existing registration or order. A linked domain receives no new local identity. All selected local registrations and their unique name claims are saved together; a later local error rolls back that entire import.

After a lost response, follow the original operation or repeat exactly the same request with the same idempotency key. Do not start a registration or transfer to bypass an import problem. This guide describes locally tested import/inspection code; global synchronization and ownership transfer have separate administration and billing plans. This is not a declaration of production deployment or general availability.

Import normalizes the documented registrar date fields to valid database dates; the existing portal choice `creation_date`/`renewal_date` remains authoritative. A zero value for `deleted_at` means not deleted. Timezone offsets are converted to UTC; invalid calendar dates return a controlled supplier error before storage.

On 29 October 2026, a read-only probe through the existing configured registrar connection checked one bounded inventory page (`limit:1`) and its corresponding detail response. Object IDs matched; only type and shape metadata were reported. The actual date fields used the supported SQL datetime form, the deletion date was empty/zero and contact handles and the nameserver group were strings. No object IDs, names, addresses, contact details, keys or supplier responses were displayed. This proves current response compatibility, not a completed production import.

## Synchronize the complete portfolio

The portal opens a separate confirmation page through **Domains → Synchronize registrar**. This covers the complete portfolio of the existing registrar integration, including registrations outside the selected administration company. Confirmation explicitly states this. The portal refreshes only local metadata: contacts, domain metadata, missing encrypted transfer codes, nameserver groups and provider detection. Registrations, transfers, DNS records and invoices are not changed at a supplier. Existing owners, company bindings, DNS routes and commercial subscriptions are preserved; unknown registrations remain unbound.

The seven phases are `holders`, `domains`, `tokens`, `nameservers_start`, `nameservers_collect`, `nameservers_apply` and `providers`. Each step processes at most 25 source objects. Nameserver observations are collected completely first; only then is the existing canonical choice of the largest observed set applied per group. An empty observation does not clear an existing set. New groups receive no implicit company or global access. The source is `domain_observation`; this is not new supplier configuration or authority to change DNS records.

Preserve one original UUID. The CLI displays the run UUID in `data.id`:

```sh
binadit admin domains registrar sync start \
  --intent 1fe95bbe-8449-4ea3-a6b2-3f013a2047bc \
  --confirm-portfolio 1 --account 1 --yes --output json
binadit admin domains registrar sync status RUN_UUID --account 1 --output json
binadit admin domains registrar sync run RUN_UUID \
  --confirm-portfolio 1 --max-pages 25 --account 1 --yes --output json
```

Replace `RUN_UUID` with the original `data.id`. `--account` and `--confirm-portfolio` confirm the administration company, not a new owner for every registration. `--max-pages` bounds one CLI invocation (1–1000, default 25). A subsequent normal invocation reads the same run and continues from the confirmed phase. Without an ambiguous pending request, you can sign in personally again with a recent passkey when needed. An ambiguous request requires the original receipt first; a new login creates another personal connection. Background renewal or execution through a machine grant is not allowed.

Before every new page, the Rust CLI records the exact phase, cursor, version, idempotency key and public personal `principal_id` in a private journal with schema `binadit-registrar-sync/v2`. The identity comes from `GET /api/v1/me`, with the same acquired bearer token used for the final page. A changed connection is refused before dispatch. The journal contains no supplier details, contact addresses, access tokens or transfer codes. The public principal identifies only the historical owner and grants no access. The CLI checks the original process lock and journal contents again after authentication, immediately before dispatch and before local publication of a receipt. The server also enforces phase, version and SQL locks. Do not use your own `--idempotency-key` for `run`: each page has its own original key.

After a lost or ambiguous response, the CLI stops immediately. Inspect the original operation and safe run status. `--recover-original` checks which personal connection is active. Within the same connection, the exact original page request can be replayed. After a new login, the CLI reads only the separate original receipt; it does not send the old page under that new connection:

```sh
binadit admin domains registrar sync status RUN_UUID --account 1 --output json
binadit admin domains registrar sync run RUN_UUID \
  --recover-original --confirm-portfolio 1 --account 1 --yes --output json
```

After successful read-only recovery, the CLI clears only the confirmed local pending marker and stops this invocation. `receipt.local_pending_resolved:true` confirms this local result. Then start the same run again without `--recover-original`; that separate invocation reads current source status and ETag before preparing another page. An earlier confirmed step is replayed without reading the supplier page again. A still-running or ambiguous step starts no replacement. If the server definitively confirms `operation_failed` for the exact key, recovery stops and releases only that local pending marker; only a separate deliberate invocation may review the current phase again. Do not delete the journal to bypass an ambiguous result. In the portal, **Check original request** works the same way: no automatic retry, followed by explicit resumption. **Pause after current page** waits for the running page; it is not supplier cancellation.

For a read-only receipt with `operation.status:failed`, the CLI displays the original operation number and retains the pending marker. Review that result. Only the following explicit confirmation may release the same local marker:

```sh
binadit admin domains registrar sync run RUN_UUID \
  --recover-original --confirm-failed-operation OPERATION_UUID \
  --confirm-portfolio 1 --account 1 --yes --output json
```

`OPERATION_UUID` must exactly match the reread failed original receipt. This Rust option works only with `--recover-original`, not `--dry-run`. The invocation also stops after this confirmation; it executes no next page. A separate normal `run` then reviews current source, progress and version. This local release applies only to the atomic registrar step with supplier reads; a failed operation in other API functions does not prove that external effects are absent.

An old pending journal without a recorded original personal principal is not adopted by the current login. A missing, running, mismatching or unreadable original receipt also remains held. Preserve the run, original key and metadata for administrator investigation. Do not add the current principal to old metadata or delete or replace the journal to force recovery. An old empty journal can be upgraded to schema v2 for a new, freshly reviewed page.

A single step can also be executed explicitly with exact values from `status`:

```sh
binadit admin domains registrar sync step RUN_UUID \
  --phase holders --page-cursor 0 --page-size 25 --version EXACTE_SHA256 \
  --confirm-portfolio 1 --account 1 --yes
```

`--dry-run` starts no run or operation record, stores no pending journal and changes no local resources. For `run --dry-run`, only the next page is read and validated. It can therefore perform supplier reads. Status inspection itself makes no supplier HTTP call. Even after supplier configuration changes or disappears, the original owned progress and operation results remain inspectable; `source_current: false` and `resumable: false` prevent further steps with a different source.

The parent run expires after two hours. Nameserver observations expire after one hour; `blocked_reason: nameserver_collection_expired` stops the run even while its parent remains valid. Other reasons are `expired`, `source_changed` and `nameserver_collection_unavailable`. Preserve the original result and review a new synchronization; do not create a new request to bypass a still-ambiguous step. External inventory has no proven immutable snapshot across every page and phase. Avoid concurrent portfolio changes at the registrar and repeat after such changes; absence from a page never silently deletes local registrations.

### API contract

- `POST /api/v1/accounts/{context}/admin/domains/registrar/synchronizations` with `{intent_id: UUID}` and one durable `Idempotency-Key` prepares one run.
- `GET .../synchronizations/{run}` returns only safe counts, phase, cursor, context, expiry times and readiness, with the current `ETag`.
- `POST .../synchronizations/{run}/actions/step` with `{phase,cursor,limit?}` (`limit` 1–25), the original `Idempotency-Key` and `If-Match` applies one step.
- `POST .../synchronizations/{run}/original-step-receipt` reads only one exact original page receipt after signing in again; this request executes no page.

An expected policy, source or version refusal is checked before a new operation record. The same checks run again under current local locks. The source result, parent/child progress and final operation result commit together. A lost response after that actual commit is recovered from the original operation. Full supplier responses and private observations do not appear in progress, audit results or CLI output. This describes the tested implementation; it declares no production deployment or general availability.

### Original page receipt after signing in again

Before the first page, retain the public `principal_id` from `GET /api/v1/me`, original key, run UUID and exact JSON input. The read-only route requires a current personal internal administrator with `admin:read`, recent passkey/two-factor verification and the same company and identity version as the original run. The old personal session may be revoked or expired, but its owner must still demonstrably be the same person and company. Machine grants and a missing old session give no access.

Send without a new `Idempotency-Key`, `If-Match` or top-level `dry_run`:

```json
{"original_principal_id":"user:019a0000-0000-7000-8000-000000000001","original_key":"original-page-key-000000000001","request":{"phase":"holders","cursor":0,"limit":25}}
```

`request` must reconstruct the original body exactly, including whether `limit` and `dry_run:false` were present. The server checks the original method, path and body with the same server HMAC. The original fingerprint did not contain `If-Match`; this receipt therefore does not confirm an old expected version. No supplier reads, new operations or mutations follow, even when supplier configuration is missing or API writes are disabled.

HTTP 200 returns the original `operation.id`, `status` and `receipt.read_only:true`. Only `succeeded` includes a safe progress projection in `data`; `running` and `failed` return `data:null`. Check the exact run, company context and valid phase transition before treating a local pending request as resolved. Then read current status and ETag before confirming another page. A missing, running, mismatching or unreadable receipt holds the original request; submit no replacement page. A failed original operation first requires review and a separate deliberate next step. The Rust CLI in this source version processes this original receipt with schema-v2 provenance and local file checks. Production release and deployment retain their separate checks; recovery of other API functions falls outside this contract.

## Change an existing registration: renewal and contacts

An existing registration can change its nameserver group, contact roles or automatic renewal setting. This is not a new registration, transfer, recovery order or immediate paid renewal. Normal routes require `domains:write`, the current domain ETag in `If-Match` and one durable `Idempotency-Key`. Preserve that key, exact JSON and first `operation.id`; an ambiguous response does not justify a new request.

| POST beneath `domains/{domain}/actions/` | Reviewed JSON |
| --- | --- |
| `nameservers` | `nameserver_group_id` and `nameserver_group_version` from group review |
| `contacts` | `role`: `owner`, `admin`, `tech` or `all`; `holder_id` of an available company contact |
| `cancel` | `confirm_domain` with the exact domain name |
| `resume` | The same exact `confirm_domain` |

Read the domain with `binadit domains get ID --account KLANT --output json`; the CLI adds the HTTP ETag in `meta.etag`. Available groups are under `domains/options/nameserver-groups`; detail returns `data.version`. The [DNS guide](/developers/guides/dns-mutations?lang=en) describes the full nameserver review and distinction between a copied `api-*` list, a live registrar group and a local-only choice.

Example cancellation at the end of the existing paid period:

```sh
umask 077
binadit domains get 41 --account 99 --output json > domain-review.json
jq '.data | {confirm_domain:.domainname}' domain-review.json > cancel.json
binadit domains actions run 41 cancel --account 99 --from-file cancel.json \
  --version "$(jq -r '.meta.etag' domain-review.json)" --dry-run
```

Review the name, company and existing agreement. Deliberately run the same command without `--dry-run` and with `--yes`. Cancellation uses `autorenew: off` at the registrar and terminates future billing through the same agreement. An already paid registration period is retained. With portal billing, the existing yearly item ends at the correct period end; with WeFact, the same subscription terminates at the end of its current period. No additional subscription, new billing contract or refund is created.

Then request subscription status with `binadit api /domains/41/subscription --account 99 --output json`, equivalent to `GET /api/v1/accounts/99/domains/41/subscription` with `domains:read`. `billing_agreement` shows the existing local subscription number, status and start/end dates; `cancellation_requested_on` and `expires_on` distinguish cancellation from the paid registration period. A WeFact subscription not yet started can end on the day before its scheduled start. This read accesses the portal and sends no supplier request. Also check the verified original operation result before treating cancellation as complete.

Before resumption, read the current domain version again and review the same agreement. Then use `domains actions run 41 resume` with the same form of domain confirmation and the **new** ETag. Resumption sets `autorenew: on` and restores that same billing agreement. An elapsed registrar deadline, changed or duplicate contract, ambiguous earlier supplier step or unavailable accounting connection is not converted into an ordinary paid renewal. Separate review is required first.

Contact changes use an existing company contact, for example a private file containing `{"role":"owner","holder_id":23}` and `binadit domains actions run 41 contacts --account 99 --from-file contacts.json --version DOMEIN_ETAG --dry-run`. Then review the real action with `--yes`. `all` selects the same contact for every role in the API; the portal can review owner, administration and technical contacts separately. A genuinely unregistered domain without a registrar object can save only the local contact choice. A registration at another supplier is not silently handled locally. Any update fee, trade or new registration is not automatically approved.

### The same actions as an internal administrator

Your approved context remains in the URL and `--account`. Every POST beneath `/api/v1/accounts/{operator}/admin/domains/{domain}/actions/{nameservers|contacts|cancel|resume}` adds `target_account_id` to the existing action fields above. These routes require current personal passkey verification, internal administration rights, `admin:write` and the actual target domain's ETag. They create no customer membership or customer grant.

An administrator can already read the target domain version through `GET admin/dns/zones/{id}`. A full nameserver choice has an SQL-only review that also returns the correct group version:

```sh
binadit api /admin/domains/41/nameserver-review/99/7 --account 900 --output json
```

Here `41` is the target domain, `99` the customer company and `7` the selected group; `900` is your administration company. The response contains both versions, `context_account_id`, `target_account_id` and delegation. The route requires `admin:read` and the same real personal passkey administrator. An incorrect target binding or group unavailable to the customer is refused without supplier HTTP. For an administration mutation, use `binadit api /admin/domains/41/actions/cancel --method POST --account 900 --from-file admin-cancel.json --version DOMEIN_ETAG --dry-run`, for example with `{"target_account_id":99,"confirm_domain":"voorbeeld.nl"}`; execute only after review with `--yes` instead of `--dry-run`.

### One original request, including after lost responses

Status is under `domains/changes/{change}`; for administration actions, under `admin/domains/changes/{change}`. Use `.../by-operation/{operation}` to locate the existing journal through the first HTTP operation. For an administration action, this lookup stays with the **operator company**, even though the registration belongs to the target company. The response contains `id` (journal), `operation_id`, `zone_id`, `version` (journal) and `zone_version`.

```sh
binadit api /domains/changes/by-operation/HTTP_UUID --account 99 --output json
binadit domains changes get JOURNAL_UUID --account 99 --output json > original.json
jq '.data | {confirm_mutation_id:.id,confirm_zone_id:.zone_id,confirm_zone_version:.zone_version,version,confirm_no_supplier_mutation:true}' original.json > reconcile.json
binadit domains changes reconcile JOURNAL_UUID --account 99 \
  --confirm-mutation JOURNAL_UUID --input reconcile.json --yes
```

For an administration action, add `--administrator` to `domains changes` and retain the original operator company in `--account`. The original HTTP operation and journal are different numbers. Use the journal number for `--confirm-mutation`.

`reconcile` sends only supplier GETs and can restore the same local or HTTP receipt. It repeats no uncertain registrar PUT or financial mutation. An original step never previously dispatched can be performed only with the separate `continue` and `confirm_continue_original:true`. A wholly undispatched request can atomically release its registrar and financial reservation with `abandon` and `confirm_abandon_original:true`. A possibly dispatched or already completed step cannot be cancelled as unused.

A cancellation that stopped before preparation of supplier steps can be continued by the same original personal CLI connection with the existing domain write rights. This applies only to exactly the same signed request with no effect plan, effects or dispatch markers; its associated financial reservation must also be completely unstarted. The server checks the connection, user, identity generation, company and original scopes again. Use the existing change number and current review versions, not a new cancellation or login. After any possible dispatch, the separate recovery route for a verified company owner or internal administrator remains applicable.

Before every recovery action, read the latest metadata and create a protected file containing exactly `confirm_mutation_id`, `confirm_zone_id`, `confirm_zone_version`, `version` and the one corresponding confirmation. Recovery sends no new `Idempotency-Key`, `If-Match`, query parameters or dry run. Rust uses `--input` for this, not `--from-file`; commands accept no new `--version`. A remaining ambiguous result returns CLI exit code `6` and retains the same original request.

Current rights remain mandatory. During a write pause, a fresh personal read connection can read the original result, perform GET-only reconciliation or release a never-dispatched reservation within the original scope ceiling. Continuation requires current write rights and enabled writes. Safe metadata exposes no registrar credentials, full supplier responses or private billing details.

### The portal

Domain details, the edit window and buttons for cancellation, resumption and contacts first open a dedicated review page. Group migration shows 25 domains per page; a contact batch contains at most 100 selected domains. Each selected registration has its own original UUID, current company rights and strong verification. Execution stops at an ambiguous result. The portal retains that number after reloading and explicitly offers status, read-only recovery, continuation or cancellation before dispatch. An unknown server record is not treated as success: only explicitly closing that same unused number makes it permanently non-executable.

Contact and nameserver changes are separate approvals. Domain cancellation changes no Hosting subscription and implicitly sends no email; handle other products separately. Browser routes require a normal portal and CSRF session and are not an API authentication alternative. This guide describes locally built and checked routes, not a declaration that the application or CLI binary has already been deployed to production.
