Skip to documentation

Exchange will be rebuilt separately and is unavailable through the API or CLI. Availability in this release

Workflow guides

Markdown

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-billing with the reviewed confirmations, paid_invoice_id, intent_id, reviewed_at, plan_version and exact period_price_excl, plus the original domain If-Match and idempotency key;
  • GET ownership-migrations/{originalIntentUUID} for safe local progress;
  • POST ownership-migrations/{originalIntentUUID}/actions/reconcile with confirm_domain_id, confirm_intent_id and current domain_version, the same version in If-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.