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.
Domain company ownership and existing billing
Implementation status: reviewed local company transfer for native portal,
free and explicitly unbilled agreements is implemented in the shared service,
API, typed CLI and the native portal confirmation screen. Existing WeFact
agreements use the separate, explicitly reviewed one-attempt migration and
read-only reconciliation workflow below. Ordinary transfer still refuses those
agreements with domain_ownership_wefact_migration_required. Registrar inspect/import commands
preserve the existing owner and are separate from company transfer.
Changing the responsible portal company must retain original invoices and paid periods. It must not rename a historical invoice, move its debt to the target company, or silently start an overlapping yearly agreement. A legal registrant trade at the registrar is a separate operation.
Portal confirmation
An administrator selects up to 25 domains and the destination company in the existing domain overview, then opens Review company transfer. This selection uses modern portal company IDs. The next page shows the original and new company, price per whole period, preserved yearly liability and future billing start for every domain. Enter the destination company number and explicitly confirm the review. A current, user-bound passkey approval is required before the server reviews or transfers ownership.
Each selected domain retains its own original request and durable receipt. An error stops the sequence; completed transfers stay completed. After an uncertain reply, use Inspect the original request. A verified completed receipt permits continuing with the next domain; an explicit resume of an unresolved request keeps the same original body and identifier. A failed inspection does not create a new request. Old unreviewed assignment submissions are rejected.
Review and transfer a supported native agreement
Use a current human administrator grant with admin:read, admin:write and
recent passkey approval. --account is the approved administration context;
--target-account is a separate modern company ID. The domain ID is the local
portal ID, not the upstream registrar ID.
binadit admin domains ownership plan 42 --target-account 99 --account 1 --output json
Read the original company, target company, target payment policy, price per
whole period, preserved original liabilities and first new billing date. Copy
domain_version, plan_version, period_price_excl, reviewed_at and
intent_id from this exact review. Add --start-billing consistently to review
and transfer only when converting a known existing free agreement to paid
future periods is intended.
binadit admin domains ownership transfer 42 \
--target-account 99 --confirm-target-account 99 --confirm-domain 42 \
--version "$DOMAIN_VERSION" --plan-version "$PLAN_VERSION" \
--period-price "$PERIOD_PRICE_EXCL" --reviewed-at "$REVIEWED_AT" \
--intent-id "$INTENT_ID" --account 1 --dry-run
The preview checks the same resource and signed plan without allocating an
operation or changing ownership. Submit the same command without --dry-run
after reviewing the real companies and periods; use the normal interactive
company confirmation, or explicit --yes for reviewed automation. Keep the
original intent and client idempotency key when inspecting an unknown result.
The same completed ownership UUID can recover its original local receipt;
it cannot be rebound to a different company, price or review. No registrar
request, invoice, payment or supplier job is created by this native transfer.
HTTP equivalents are GET /api/v1/accounts/{context}/admin/domains/{domain}/ownership-plan
with target_account_id and start_billing, followed by
POST /api/v1/accounts/{context}/admin/domains/{domain}/actions/transfer-company.
The POST requires the exact domain If-Match, durable Idempotency-Key and
reviewed confirmation fields. Original invoices and previous complete yearly
liabilities stay at the source company. Old private contact pointers are
removed; registrant contacts, DNS provider configuration and hosting ownership
are not changed. A foreign private DNS connection/group, pending scheduled DNS
change or unresolved original purchase must be resolved explicitly first.
Existing native yearly agreement
DomainOwnershipPeriods is a read-only planner using the same addMonths
progression as the canonical monthly invoice planner. It accepts complete yearly
periods with a coherent paid cursor and no pending invoice or cancellation.
It preserves every already-started year and unbilled backlog at the source
company; the target's first period starts on the next original annual boundary.
Already billed future years remain covered through their exact original end.
For example, an unbilled January–December 2026 year remains owed to the original
company when responsibility changes in October. The new company's first year
starts on 1 January 2027. A contract already billed through 31 December 2028
cannot start charging the new company before 1 January 2029. Tests run the real
native monthLines() planner against these proposed old/new boundaries. This
component itself creates no replacement agreement or ownership change.
Existing WeFact agreement
DomainOwnershipCoverage performs only subscription.show and invoice.show
through the configured canonical accounting client. It checks fresh actor,
company, resource, debtor and local contract bindings before and after supplier
reads. It produces a fixed metadata projection; it never stops an agreement,
issues a payment or changes local ownership.
The supported interoperability subset requires all of the following:
- One exact local domain agreement and one exact remote contract/debtor in EUR.
- Quantity one, matching product/base price and a whole-year billing period.
- An explicit current cycle whose inclusive end equals start plus the contract period minus one day, and whose next invoice date is the following day.
- For a paid contract, an explicitly selected fully paid supplier invoice and
exactly one line whose
PeriodicID, quantity, product, price, discount, period unit/count and inclusive dates match that contract and cycle. - No known local withdrawal, refund, cancellation, foreign invoice binding or duplicate legacy company mapping.
A zero-cost agreement can be proved without inventing a paid invoice. Missing,
ambiguous or inconsistent proof fails with
domain_billing_coverage_unverified; a missing selected invoice for a paid
agreement returns domain_paid_period_review_required.
The primary subscription documentation and invoice documentation describe these fields. Their examples do not prove the complete relation or current-cycle semantics for every historical WeFact installation. The checks above are explicit runtime preconditions, not a claim that all historical agreements satisfy them. A read-only production selection found no active local domain agreement, so no actual supplier agreement was available to verify this representation. Fixtures demonstrate the accepted and rejected protocol shapes; they are not production evidence. An incompatible agreement requires accounting reconciliation before any ownership or billing change.
The private read-back helper also pins the configured accounting source. It
re-reads the same contract and selected paid invoice, compares all reviewed
commercial fields and the exact invoice-line identity, and requires
TerminationDate to equal the approved inclusive coverage end. A different
source, missing or different termination date, changed line or withdrawn payment
cannot confirm the original agreement. This helper makes no local changes.
The separate shared migration service implements a durable attempt and read-back boundary. Preparation and its single dispatch reservation commit atomically before accounting HTTP. An uncertain reply retains the original company and intent; reconciliation reads the exact original paid contract before atomically creating a future portal agreement. The new agreement starts the day after the original inclusive coverage end, and the former contract and invoices keep their original company. The original signed cutoff can also be reconciled after that date has passed; a new review still requires a current cycle.
Review and migrate an existing WeFact agreement
In the portal, select one domain and open its WeFact migration from the normal company-transfer page. The company account's domain selector also opens this review; its former direct assignment endpoint cannot change the owner. Enter the exact remote WeFact invoice Identifier, then review the original company, target company, paid coverage, future complete-period price and target payment policy. Confirm the target company number and the specific migration with a current personal administrator passkey. A zero-cost original contract requires explicit zero-cost review; missing invoices are never treated as paid coverage.
The typed CLI uses the same service and keeps the administration context separate from the target company:
binadit admin domains ownership plan-wefact 42 \
--target-account 99 --paid-invoice-id 900 --account 1 --output json
Use --zero-cost-contract instead of --paid-invoice-id only for a proved
zero-cost original agreement. Copy the exact returned fields into the command:
binadit admin domains ownership migrate-wefact 42 \
--target-account 99 --paid-invoice-id 900 \
--confirm-target-account 99 --confirm-domain 42 \
--version "$DOMAIN_VERSION" --plan-version "$PLAN_VERSION" \
--period-price "$PERIOD_PRICE_EXCL" --reviewed-at "$REVIEWED_AT" \
--intent-id "$INTENT_ID" --account 1 --dry-run
Remove --dry-run after review and explicitly confirm the administration
company, or use --yes --no-input for reviewed automation. The CLI re-reads the
same signed plan at its original timestamp. Preview creates no operation,
client mutation key, termination or ownership change. Real submission persists
one stable client idempotency key before one POST; a response is accepted only
after its exact domain, target, original intent, cutoff and future start match.
The local key is retained after a failed or contradictory response.
If the outcome is unknown, the CLI exits with code 4 and reports the original migration UUID and request key. Do not obtain a new plan to retry an uncertain termination. Inspect the original receipt first; this request makes no supplier calls:
binadit admin domains ownership migration-status 42 \
--intent-id "$INTENT_ID" --account 1 --output json
For a pending receipt, use its current domain_version, original target,
price and intent for read-only reconciliation:
binadit admin domains ownership reconcile-wefact 42 \
--intent-id "$INTENT_ID" --confirm-intent "$INTENT_ID" \
--target-account 99 --confirm-target-account 99 --confirm-domain 42 \
--version "$CURRENT_DOMAIN_VERSION" --period-price "$PERIOD_PRICE_EXCL" \
--account 1 --yes --no-input
New terminations reserve the canonical subscription journal and the original ownership/API receipt in one local commit. The same original intent, source, company, contract and paid cutoff bind both journals. The one supplier dispatch starts only after that reservation commits; its acknowledgement survives a later local failure. Mirror termination, target ownership, replacement agreement and original API result then publish atomically after read-only confirmation. Previously deployed ownership receipts keep their original read-only recovery; they never allocate a new supplier mutation to replace an uncertain old request. An already scheduled exact supplier cutoff likewise needs no new termination.
After a killed worker, its durable company claims stay held. A Binadit operator must use the stopped-producer recovery on the recorded host to prove that producer has stopped and release only its exact claims. Then inspect and reconcile the same original migration UUID. Releasing a claim does not resend or confirm a financial mutation.
Reconciliation checks the original paid contract and exact termination date,
then finishes only the original local handoff. It cannot send a termination.
It remains usable after the original coverage date has passed. A concurrent
domain edit can require a fresh metadata inspection and version; this changes
only the read-only reconciliation request, not the original financial intent.
An explicit migrate-wefact --recover --confirm-intent "$INTENT_ID" with the
entire unchanged original argument set can replay the original POST/key after
its receipt is inspectable. It never obtains a new quote or retries by itself;
prefer the read-only reconciliation command for a pending termination.
The portal preserves the same original intent and body before dispatch. After an ambiguous response, Inspect is required before another action is enabled. A pending receipt enables only read-only reconciliation. A proved absence permits an explicit replay of the same request; it never allocates a replacement UUID. Lost browser storage can recover a server-known intent by inspection.
HTTP equivalents under /api/v1/accounts/{context}/admin/domains/{domain}:
GET wefact-ownership-plan?target_account_id=99&paid_invoice_id=900;POST actions/migrate-company-billingwith the reviewed confirmations,paid_invoice_id,intent_id,reviewed_at,plan_versionand exactperiod_price_excl, plus the original domainIf-Matchand idempotency key;GET ownership-migrations/{originalIntentUUID}for safe local progress;POST ownership-migrations/{originalIntentUUID}/actions/reconcilewithconfirm_domain_id,confirm_intent_idand currentdomain_version, the same version inIf-Match, and its own stable idempotency key. Dry-run is supported.
Current actor, context, passkey window, company bindings and target payment policy are checked again at the actual effect boundaries. Original invoices, old company contract and its whole paid period retain their source company. The replacement native agreement starts the following day; migration creates no current-period invoice, payment, registrar change or DNS-provider change. An original failed API operation remains historical; follow the original migration receipt and the reconciliation operation for the resolved result.
Tests use synthetic supplier responses and a disposable real MariaDB instance; no production termination was performed. Runtime commercial proof remains mandatory: an unsupported historical date/invoice representation is refused before termination rather than guessed.
Rust CLI receipt handling
The Rust handlers validate the actual portal DTOs, including exact company and domain IDs, signed review versions, two-decimal period prices, current ETags and original operation metadata. A native company transfer allocates its own transfer_id; a WeFact migration's transfer ID is its original business intent UUID. Neither identifier is an API operation ID.
For migrate-wefact --recover, a verified completed original migration is returned after its read-only status GET and the command stops without POST. An incomplete original migration remains subject to the explicit original-request and reconciliation rules above. After reconnecting, use documented read-only inspection; the generic CLI principal/recovery release gate must pass before cross-connection mutation replay is supported. A failed or missing response never permits a replacement termination or intent. --wait follows only a validated returned operation and does not submit another ownership mutation.