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.
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
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.
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:
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:
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:
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:
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:
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/synchronizationswith{intent_id: UUID}and one durableIdempotency-Keyprepares one run.GET .../synchronizations/{run}returns only safe counts, phase, cursor, context, expiry times and readiness, with the currentETag.POST .../synchronizations/{run}/actions/stepwith{phase,cursor,limit?}(limit1–25), the originalIdempotency-KeyandIf-Matchapplies one step.POST .../synchronizations/{run}/original-step-receiptreads 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:
{"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 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:
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:
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.
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.