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

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

```sh
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.

```sh
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](https://developer.wefact.com/subscription/show)
and [invoice documentation](https://developer.wefact.com/invoice/show) 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:

```sh
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:

```sh
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:

```sh
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:

```sh
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](/developers/guides/workspace-billing-fences?lang=en) 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.
