Domains

Domain transfer-in

An operator can transfer a domain from a different registrar account to the registrar account of the hosting provider, with the auth code of the domain. This has the same steps as "Domain transfer of a migration", without a migration: the deployment gets the code in its KV, and the builds of the deployment transfer the domain.

A transfer is a real order at the registrar, and the registrar charges the registrar account of the hosting provider for it, usually the price of a renewal of one year. A transfer that the registry refused can have a cost too. Thus each request that orders a transfer must accept its cost with accept_transfer_cost: true: domain_transfer_in, a submit with domain_auth_code, and a migration import or its preview with a domain_transfer that the import sends to the submit. Without it, api gives invalid_configuration with details: {"transfer_cost": "not_accepted"}, and orders nothing. A preview gives this refusal in its refusal. A migration import that keeps the domain in the account of the provider orders no transfer, thus it does not need accept_transfer_cost. The panel asks the operator to accept the cost, and sends accept_transfer_cost only after that: in the form of domain_transfer_in, in the deploy dialog with domain_auth_code, and in a migration import with domain_transfer.

POST /deployments/domain_transfer_in/{deployment_id} takes the domain and the auth_code. The stored configuration of the deployment must register the domain, else the call gives conflict. An empty code, or a code of more than 255 characters, gives invalid_configuration. The call also gives conflict for a deployment with nothing deployed, thus it does not start a destroyed deployment again. It gives conflict for a domain that a different deployment claims, for a domain that the registrar account of the hosting provider holds, and for a name below a registrable domain. An api without a registrar command, or a failure at the registrar, gives runtime_error.

The call writes the code to the KV of the deployment and starts a build of the stored configuration. That build orders the transfer and then fails, because the transfer is not complete. A deploy after the registry completes the transfer adopts the domain and deletes the KV entry. Nothing starts that deploy automatically. A second call before that writes the code again and starts a new build.

The stored acceptance of the domain terms applies, because the configuration does not change. The audit event has the action domain_transfer_in and the domain, not the code. The code is not in a job argument.

The registrar account holds a domain with a transfer that the registry refused (FAI), and the state of the deployment has that transfer. The graph does not send it again, thus this call does so with the given code, through the retry-transfer verb of the registrar command, and the call has no conflict for that domain. All refusals of the submit come first. A call to the registrar that fails gives runtime_error, and when the domain does not have the status FAI at that moment, the call gives conflict. In both cases no build starts. The registration and the state of the deployment stay. The build of this call fails until the registry completes the transfer, as for a first transfer. The deploy after the transfer records its transfer event with the quote of the deployment, as build_finished records it after a release. The first order keeps its event, thus GET /usage can show two transfers of the domain. If a refused transfer has a cost at the registrar is not known.

A domain that nobody has is registered and not transferred, because the graph transfers only a domain that a different account holds.

A submit can give the code in domain_auth_code, for the domain that its configuration registers. The first deploy of a deployment gives the code in this way, because domain_transfer_in needs a deployment with a deploy. The domain terms of the submit apply as for each submit. The configuration must register a domain, else the submit gives invalid_configuration. The code and the domain have the same refusals as for domain_transfer_in, except for the refusal of a deployment with nothing deployed, and with conflict for a domain with the status FAI: only domain_transfer_in sends that transfer again. The callback must allow domain_transfer_in for the deployment too, else the submit is refused.

The submit writes the code to the KV of the deployment and starts its build. The build orders the transfer and then fails until the registry completes the transfer, and the operator then deploys again, as above. api does not show the code: the response, the deployment, the audit events and the schema (writeOnly) do not have it. The audit event of an accepted submit with a code has the domain.

transfer_in of domain_list names a visible deployment that can make this call, or is null. When the last read of the registrar account shows the domain with a status other than DEL or FAI, or shows a domain above it, it is null. An unregistered domain names the first deployment with a stored configuration that registers the domain and with something deployed. A transferred domain does the same, without the deployment of the record. A held domain names its holder when the stored configuration of the holder registers the domain and a read of the registrar account at or after the last build of the holder that succeeded does not show the domain, or the last read shows it with the status FAI. Any other domain has null.

transfer_failed of domain_list is true when the last read of the registrar account shows the domain with the status FAI, and the holder of the domain, if there is one, is visible. The read does not tell the cause. With transfer_in, that deployment can make the call above. After such a call, transfer_failed stays until the next read, and a second call then gives conflict.

transfer_in_progress of domain_list is true when the last read of the registrar account shows the domain with the status REQ or PEN, and the holder of the domain, if there is one, is visible. A transfer can take some days. When the transfer is complete, a deploy adopts the domain. The hourly sweep of api does not delete the zone of such a domain.

Domain claims

The ledger of the domain claims of the hosting provider (see "Assign a domain to a deployment" in the hosting provider guide) tells which deployment has each domain of the registrar account. A deploy of a deployment that registers its domain writes the claim, and its destroy deletes it. api reads and writes the ledger with the token of the operator deployer.

domain_list gives the domains that the deployments of the caller register in their stored configuration, or that a build of them that succeeded registered. The domain that a build registered is the value that the build recorded at its submit, as in usage_list, thus a later change of the curation does not change it. A destroyed deployment is in the scope, so that its owner can give its domain to another deployment. The claim and the builds of all deployments decide the status of a domain:

  • The claim decides when it names a deployment. The domain is held while that deployment is not destroyed, also when it now registers another domain, because its next deploy deletes the claim.
  • With no claim, the deployment whose build registered the domain last decides. The domain is held while that deployment is not destroyed and its last build registered the domain.
  • Else the domain is free to take: assignable when the caller can see the deployment that had it, else unavailable. A claim that names a deployment that api does not know gives unavailable. A domain that no build registered is unregistered.
  • A domain that the target of a migration or a take-out took over is transferred, see "Domain transfer of a migration" and "Domain take-out".

The list names a deployment only when the caller can see it. With ?domain=, the list has only that domain, and api reads only its claim.

A held or assignable domain with a holder also gives the domain of its registration (registered_domain), when that registration expires (expires_at), and if the registrar renews it then (autorenew). renewal_source tells where the values come from, and renewal_as_of gives the time of that source:

  • registrar: the last read of the registrar account, the file that registrarSnapshot of the NixOS module of api points to. This source applies when the read is at or after the end of the last build of the holder that succeeded. The time is the time that the file changed. A domain that the read does not show, or shows as deleted, has null values.
  • deploy: the last build of the holder that succeeded, when no read is after it, or without registrarSnapshot. This source applies only when the read or a domain event has the domain. The expiry is the latest that the domain events of the domain give. The last build of the holder that registers the domain decides the renewal: it is autorenew of domainRegistration in the curation that api has now, or off when that build has the value of Domain renewal. A later destroy, or a later deploy that registers a different domain or no domain, does not change the renewal (on_destroy = "retain" in registrar.tf). With no such build, autorenew is null.

A name below a registrable domain, such as demo.example.eu below example.eu, has no registration of its own: its deploy buys nothing and records no domain event (registrar.tf). api has no list of the extensions. A registrable domain has no registrable domain above it, thus when the last read does not show the name, but shows a domain above it, the name is such a name. Then the values are those of the nearest domain above it in that read, with the source registrar, whatever the time of the last build. registered_domain is that domain. A deploy of the name does not change that registration. Without registrarSnapshot, or when the read does not show a domain above it, the name has null values.

The values describe the registration in the registrar account of the hosting provider. They do not tell who pays for the registration (domain_events of usage_list), or who decides its renewal: autorenew of the curation decides it, which the hosting provider sets, and the operator can only turn off a renewal that it allows (Domain renewal). A domain that a deployment registers before the domain events, and that the read does not show, has null values.

The other domains have null values.

domain_claim gives the domain that the stored configuration of a deployment registers to that deployment. It writes the claim with a check-and-set on the version that it read, thus a claim that changes during the call gives conflict. It queues no build: the next deploy of the deployment adopts the domain. When the domain is free to take from another deployment, api asks the callback again, with that deployment in deployment_ids, before it writes. api refuses the call with conflict when the domain is held by another deployment, when the claim names a deployment that api does not know, when no build registered the domain, or when the domain is transferred. A deployment whose configuration registers no domain gives invalid_configuration.

domain_assign does the same as domain_claim, and also gives a domain with a claim that names a deployment that api does not know, and a domain that no build registered. No deployment of api can release these domains, thus api asks the callback only about the target. With the query parameter domain, the deployment takes that domain in place of the domain of its stored configuration, thus a domain of the registrar account that the deployment did not have yet. Its next deploy adopts the domain when its configuration then has that domain. api refuses with invalid_configuration a domain that is not a domain name, a domain that a deploy of the deployment does not register at the registrar account, and a deployment with no stored configuration. The callback gets domain_assign as the procedure, thus it can keep these domains with the staff of the hosting provider. api refuses the call with conflict when the domain is held by another deployment, or when the domain is transferred. A holder gives up its domain when it is destroyed, or when a deploy of another domain deletes its claim.

Domain registration

A submit can register a new domain, and a registration costs the hosting provider money. Thus api asks the authorization callback a second time for such a submit, for the procedure deployment_submit and with the domain as scope.domain. A front-end with billing can thus refuse the registration of a domain to an operator, for example per extension, and accept the other submits of that operator. A callback rule that does not read scope.domain gives the same answer as for the first call.

The answer of the callback to a submit or to an import can give domain_contacts, an object from the roles owner, admin, tech and billing to IDs of domain contacts of the operator (see "Domain contact"). api records the IDs on the deployment, and each build gives the handles of these contacts to the registration as TF_VAR_domain_contacts, an object from each role to a handle. A role with no ID, or with an ID without a known handle, gets the handle of the owner. A new registration and a transfer have the handle of owner as the owner, and the handles of the other roles as the administrative, the technical and the billing contact where the extension has them. On a domain that exists, the administrative, the technical and the billing contact follow their handles, and the owner stays, because a change of the owner is a trade at the registry. The IDs come from the callback and not from the body of the call, because the contacts of all operators are in one registrar account, and the front-end decides which operator has an ID. An empty string is a role with no contact of its own. An answer without the field keeps the recorded IDs, and an automatic update does the same. A key that is not a role, or a value that is not an empty string or a contact ID, denies the call.

A submit that registers or transfers a domain needs a recorded owner with a known handle, and a known handle for each other role with an ID. Otherwise api refuses it with conflict, also without an authorization callback, details gives domain_contact as missing and the role, and the message tells the operator to save that domain contact first. Thus the operator is the owner of each domain that api registers, and the hosting provider is never the owner, also when a front-end does not call "Domain contact". The refusal comes after the checks of the caller, of the terms and of a suspended deployment. A submit for a domain under a domain of the deSEC account of the provider registers nothing, thus it needs no contact.

api asks when the configuration registers a domain that the deployment does not have, and that the registrar account does not hold. A deployment does not have the domain of its stored configuration after a destroy. api does not ask for a domain under a domain of the deSEC account of the provider, because the deploy registers nothing, and not for a submit with domain_auth_code, because domain_transfer_in decides on that. If api cannot read the registrar account, the submit fails. Without a registrar command, a domain counts as not held.

api asks after it migrates the configuration, and before it checks the domain terms. A refusal is a 403: api stores nothing and starts no build. The audit event has the action deployment_submit, the domain and the reason of the callback.

api does not ask without a callback. It does not ask for a submit that does not come from a request, as an automatic update, and not for a migration import, which transfers or claims its domain.

Domain of an application

The data of Mastodon and of Peertube holds the domain that the application is served at, thus these applications cannot move to a different domain. A submit whose configuration serves one of them at a domain that is not the domain of its kept data is refused as invalid_configuration, and nothing changes. The kept data has the domain of the newest succeeded build of the deployment that enabled the application. A build that did not succeed does not change it. A succeeded destroy, or a succeeded build with enable false and deleteData true for the application, deletes the data, thus the next submit can take a different domain. A submit with enable false for the application is not refused.

Domain extensions

The curation can list the extensions that the hosting provider registers, in domainRegistration.extensions of the deployment method. The extension of a domain is all its labels after the first, as co.uk of example.co.uk. A submit, also of a migration import, that registers a domain with an extension that is not in the list is refused as invalid_configuration, and nothing changes. A submit of the domain that the stored configuration of the deployment registers is not refused, thus a removal from the list does not stop the deployment that has the domain. A submit of a domain that the registrar account holds is not refused either, as the deploy adopts it whatever its extension: the api reads the account with REGISTRAR_COMMAND, and refuses the domain as runtime_error when the registrar fails. Without that command, the api cannot read the account, thus it refuses the domain. Without the list, each extension is allowed. The deploy makes the same test.

Domain terms

The domain terms of a domain are the terms that its registration or its transfer needs the acceptance of. They have two parts:

  • The terms of the hosting provider: a URL and a version (API_DOMAIN_TERMS_URL and API_DOMAIN_TERMS_VERSION, domainTerms of the NixOS module), both or none. API_DOMAIN_TERMS_EFFECTIVE can give the date from which these terms apply.
  • The terms of the registry of the extension of the domain, from the table at API_REGISTRY_TERMS_FILE (registryTermsFile of the NixOS module, by default core/effects/tf/incus/operator/registry-terms.json). An entry of the table has the documents of the terms, each with a title, a URL and a sha256, their version, and the flags that the deploy sends to the registrar at a registration (acceptance) and at a transfer (transferAcceptance). It can also have effective, the date from which its terms apply, and noticeDays, the days of notice that the registry gives. An extension without an entry needs no acceptance. An entry without documents has terms that the table does not have: api refuses each domain with this extension as invalid_configuration, the same as an extension outside domainRegistration.extensions.

GET /domains/terms?domain=<domain> gives the terms of domain without authentication: the documents of the two parts, and one version, or null when the domain needs no acceptance. Each document has effective, the date from which its part applies, or null when the date is not known. Without domain, it gives only the terms of the hosting provider. The version has one token for each part, separated by a space: provider@<version> and <extension>@<version>, as provider@2026-01 dk@15. Thus a new version of one part needs a new acceptance.

A submit, also of a migration import, whose configuration registers a domain must give the current version of the terms of that domain as accepted_domain_terms, unless the deployment has that domain already: its stored configuration registers the same domain and it is not destroyed. Else api refuses it as invalid_configuration, and nothing changes. The message gives the URLs of the documents, and details.domain_terms of the response gives these terms in the form of GET /domains/terms. A configuration registers a domain when its domain is outside the apex of domainRegistration in the curation. A configuration that registers no domain needs no acceptance.

The audit event of a submit or of an import records the version that the caller gave, in domain_terms, which is in the hash chain. A submit that gives the current version also records the version and its time on the deployment, in domain_terms and domain_terms_accepted_at. Thus a new version of the terms does not stop the deployment that has the domain: its submits go on, and the deployment keeps the version that the operator accepted until the operator accepts the new version. A deployment that takes a domain, at its first submit with that domain or at a migration import, needs the acceptance of the current version. An automatic update does not check the terms, because no person submits it, and it does not change the recorded version.

GET /domains gives domain_terms for a domain that a deployment of the caller has: the version and the documents of its current terms, accepted, the version on the deployment, and accepted_at, the time of that version. When accepted is not version, the acceptance is pending. A deployment with no recorded version has accepted: null, thus it is also pending. The field is null when the domain needs no acceptance, or when no deployment of the caller has the domain.

The operator cannot take back an acceptance while the deployment has the domain. To end the terms, the operator destroys the deployment, deploys a configuration that registers no domain, or changes to a domain that needs no terms. A succeeded build of these kinds clears domain_terms, domain_terms_accepted_at and domain_terms_notified on the deployment, unless a newer build of the deployment exists. Each build records the version of the terms that its domain needs at the submit, thus a build with no version ('') is a build of a domain that needs no terms. The next submit that registers a domain needs the acceptance of the current version again, also for the same domain. A build that fails clears nothing, because the deployment still has the domain. A deployment that changes to a different domain that needs terms gives the acceptance of the terms of that domain at the submit, and this replaces the version on the deployment. The audit events keep the earlier acceptances.

PUT /deployments/domain_terms/{deployment_id} with the body {"version": <version>} records the acceptance of the deployment: it sets the version and its time on the deployment, and records the version in an audit event domain_terms_accept. It accepts only the current version of the terms of the domain of the stored configuration. It answers 409 for a different version, or when the domain needs no acceptance, 400 when the configuration registers no domain, and 404 for an unknown deployment. It does not start a build, and a pending acceptance does not stop the deployment or its domain.

PUT /deployments/domain_terms_notice/{deployment_id} with the body {"version": <version>} records that the operator had a notice of a version: it sets the version on the deployment, and records it in an audit event domain_terms_notice, in domain_terms, which is in the hash chain. Thus for this action, domain_terms is the version of the notice and not an acceptance. Only an internal call can do this, and a different caller gets 403, thus the audit event tells that the provider gave the notice. It has the same checks as the acceptance. GET /domains gives the version as notified, in domain_terms. The hosting provider's panel uses it to send one notice for each pending version. The api stores no personal data of the notice.

Domain renewal

An operator who does not want to keep a domain can turn off its renewal. Then the registration ends when it expires, and the operator pays no renewal. The hosting provider decides each other renewal: an operator cannot turn on a renewal that autorenew of domainRegistration turns off.

PUT /deployments/domain_renewal_off/{deployment_id} stores the domain of the stored configuration on the deployment, in domain_renewal_off, and starts a build of the stored configuration, as a suspend does. PUT /deployments/domain_renewal_curated/{deployment_id} clears the value, and starts the same build, and the domain has the renewal of the curation again. The deploy changes the registration, and autorenew of GET /domains shows the change after that build. GET /domains gives renewal_off, which is true when the deployment has the value for this domain, also before a build applied it. It is null when the domain has no registration of its own, or when no deployment has the domain.

The value is a domain and not a flag, thus it applies only to that domain: a deployment that changes to a different domain gets the renewal of the curation. The value applies until a destroy of the deployment succeeds. A migration does not copy it: the target has the renewal of the curation.

The authorization callback gets each direction as its own procedure, thus it can allow one direction and refuse the other. The api knows no billing. A front-end that makes the renewal depend on billing does this in its callback and with its service token: the callback refuses domain_renewal_curated for an operator who did not pay, and the front-end calls domain_renewal_off when the payment stops. The domain then keeps its registration until it expires. The default panel allows both directions to the operator of the deployment.

domain_renewal of x-actions gives the variable that takes the value. A deployed revision without this entry cannot change the renewal, and the call gives invalid_configuration. The call also gives invalid_configuration when the configuration registers no domain, or when the domain is a name below a registrable domain. It gives conflict when nothing is deployed, when the last build applied the value and did not fail, and when the domain went to the target of a migration. It gives queue_full when a build runs.

The audit event of the call has the action domain_renewal_off or domain_renewal_curated, and it records the domain. A denied call records the same operation.

Domain take-out

An operator can move a domain to a registrar account of their own, and change the renewal of a domain that no deployment uses. These four calls name the domain in the body, and a deployment in the path: the deployment that had the domain. That deployment can be destroyed, thus the authorization callback gets the deleted deployments of the operator for these procedures.

Each call needs a domain whose standing (see "Domain claims") names the deployment in the path. A deployment that is not destroyed and still has the domain, or a domain that a different deployment has or claimed, gives conflict. A different deployment that is not idle and has a stored configuration that registers the domain also gives conflict, as does a domain that a transfer took. A domain that the registrar account of the provider does not hold gives conflict: a name below a registrable domain has no registration, thus it gets this refusal. An api without a registrar command, or a failure at the registrar, gives runtime_error.

POST /deployments/domain_take_out/{deployment_id} reads the auth code, unlocks the domain, removes its DNSSEC keys and writes the marker of the record, as a migration export does (see "Domain transfer of a migration"). The result gives the code, and the api does not keep it: a new call gives the code again with a new marker. The registry can refuse a transfer soon after a registration.

While the code is out, transfer_pending of domain_list is true, and a submit that registers the domain gives conflict, also for a different deployment, because the transfer can move the domain from that deployment. An import is not refused. domain_claim_write is not refused, because it locks the domain and the code stops working. After the transfer the status is transferred, and a submit of a different deployment is not refused, because the code has no effect at the registrar account of the provider.

POST /deployments/domain_take_out_cancel/{deployment_id} locks the domain, resets its auth code and removes the record. A deployment without a take-out of the domain gives conflict. The call does not set the DNSSEC keys again: a next deploy of the domain does that.

PUT /deployments/released_domain_renewal_off/{deployment_id} and PUT /deployments/released_domain_renewal_curated/{deployment_id} set the renewal at the registrar, and do not start a build. The second call needs autorenew of domainRegistration in the curation to be true for the deployment method of the build that registered the domain, else it gives conflict. domain_list shows the new value after the next read of the registrar account. A next deploy of the domain sets the renewal of that build. The authorization callback gets each direction as its own procedure, as for "Domain renewal".

Each call records an audit event with the domain and with the action domain_take_out, domain_take_out_cancel, released_domain_renewal_off or released_domain_renewal_curated. The audit event does not record the auth code.

Domain change

A deployment changes to a different domain with a submit of a configuration that has that domain. For the old domain, a succeeded deploy has the same result as a destroy:

  • The deploy deletes the claim of the old domain, and the deployment no longer has that domain. The old domain is assignable, see "Domain claims". Until the deploy succeeds, the old domain stays held.
  • The registration stays in the registrar account of the provider. It keeps the renewal that it has, thus the registrar renews it at its expiry if the renewal is on. The change of domain does not release the name at the registrar.
  • The operator can turn off the renewal of the old domain with released_domain_renewal_off, and can move it to a registrar account of their own with domain_take_out, see "Domain take-out". Then the old domain ends at its expiry, or leaves the account of the provider.
  • The zone of the old domain stays at deSEC while the registrar account holds the domain. When the domain left the registrar account (its expiry passed without a renewal, or it moved to a different registrar), the hourly sweep of api deletes the zone, with its records and its DNSSEC keys. The sweep does this only after 7 days from the release event of the domain, only for a domain that a deploy registered or transferred in, and only when no deployment has the domain and no zone follow uses the zone. The zone also stays when the last read of the registrar account (REGISTRAR_SNAPSHOT) shows the domain with a status other than DEL or FAI, for example a transfer back to the account that is not complete (REQ or PEN). When REGISTRAR_SNAPSHOT is set and api cannot read it, the sweep deletes no zone. The apex zone of the provider and the zones below it stay.
  • A different deployment can register the old domain, and needs the acceptance of its terms. The same deployment also needs that acceptance when it changes back, see "Domain terms".

Domain contact

An operator keeps their own contact at the registrar account of the provider. A contact has an ID, a UUID in lower case, that the front-end makes and keeps for the operator. api keeps a table from each ID to its handle at the registrar, and only the calls of this section write it. api has no table of operators: the authorization callback gets the ID in contact_id of the scope, and decides if the requester can use it. All contacts are in one registrar account, thus a front-end must give an ID to one operator only. api trusts the callback on this.

PUT /domain_contacts/{contact_id} makes the contact at the registrar account equal to the body, and records the handle for the ID. The result gives the handle. The call runs the command at REGISTRAR_CONTACT_COMMAND, which applies an OpenTofu root with a state of its own for each contact, and it returns when the apply ends. The contact goes to the command on stdin, not as an argument. A change of a name gives a new handle, because Openprovider does not let a name change: the contact gets a new customer. The command then deletes the old customer, unless a domain of the account names it. Other changes keep the handle.

GET /domain_contacts/{contact_id} gives handle and contact from that state, and does not read the registrar account. Both are null before the first PUT. A read also records the handle, thus a table that lost a row gets it again.

The front-end gives the IDs as domain_contacts in the answer of the callback (see "Domain registration"). A registration needs the handle of the owner. A domain that is registered keeps its owner until a call of "Domain owner".

An api without the command, or a failure at the registrar or at the state backend, gives runtime_error. The command cannot tell a contact that the registrar refuses from an outage, thus a refused contact also gives this error, and the message gives the cause. A body that does not agree with the contract gives invalid_request.

DELETE /domain_contacts/{contact_id} erases the contact: the command deletes the customer at the registrar account and the state of the contact, and api removes the handle from its table. The result gives handle and contact as null. While a domain of the registrar account names the contact, as its owner or in a different role, the call changes nothing and gives conflict. An ID with no contact is not an error. A later PUT with the same ID makes a new contact.

The PUT and the DELETE record an audit event, with the actions domain_contact_put and domain_contact_delete. A GET records none. The events do not record the contact.

Domain owner

PUT /deployments/domain_owner_change/{deployment_id} makes the owner contact of domain_contacts of the answer of the authorization callback the owner of a registered domain at the registrar. It does not start a build. The body names the domain, and cannot name a contact. An answer without an owner, or with an owner without a known handle, gives conflict. The call records domain_contacts on the deployment, thus the next deploy applies the other roles.

The call needs a domain whose standing (see "Domain claims") names the deployment in the path, as held or as released. The deployment can be destroyed, thus the authorization callback also sees deleted deployments for this procedure, and the callback gives domain_contacts for it. A domain that a different deployment has or claimed gives conflict, and a name below a registered domain gives conflict, because the account does not have that name. A domain with an auth code that is out gives conflict, because that code can move the domain during the change.

The call gives the handle on stdin to the change-owner verb of REGISTRAR_COMMAND, which reads the domain, and writes only when the handle is not the owner yet. Thus a repeat is safe. When the registrar does not allow a direct change, the result is conflict: the domain is in a process at the registrar, or its registry needs a process that this api does not do. A failure at the registrar gives runtime_error. pending of the result is true when the registrar accepted the change but does not show the handle yet.

When the domain needs terms (domain_terms), the call needs their current version: accepted_domain_terms in the body, or else the version that the deployment accepted. Any other version gives conflict, and a repeat with the current version continues. The call stores accepted_domain_terms on the deployment, as a submit does. A registrar can make the agreement of the new owner a condition of the change, and these terms can authorize the registrar as Designated Agent of the old and the new owner.

owner_handle of domain_list gives the owner from the last read of the registrar account. It is null for a domain of which that read has no entry, and for a name below a registered domain, because a different operator can have that registered domain.

registrar.tf ignores a change of the owner, thus a next deploy keeps it. The administrative, the technical and the billing contact follow the handles of the deployment at its next deploy.

Each call records an audit event with the domain and with the action domain_owner_change. The event does not record the handle.

Own zone token

A submit can give zone_token: a token of a deSEC account of the operator that holds the zone of the domain that the configuration registers. Before the deploy, api reads the zone with that token. It refuses the submit as invalid_configuration when deSEC refuses the token, when the account of the token does not hold the zone, when the hosting provider holds no zones at deSEC, or when the domain is the apex of domainRegistration or is under it. Then api writes the token to the KV entry of the deployment, in place of a token of the hosting provider, and deletes the tokens that the hosting provider made for the deployment. api does not make, change or delete a token at deSEC for that zone, and does not make the zone in the account of the hosting provider. deSEC does not show the policies of a token to that token, thus a token that cannot write the records of the deploy stops the deploy at its records step.

api does not show the token: the response, the deployment, the audit events and the schema (writeOnly) do not have it. A later submit without zone_token, an action and an automatic update keep the stored token while the zone of the deployment does not change. A submit with a different domain and without zone_token gets a token of the hosting provider. The networks of domainRegistration.zones.allowedSubnets do not apply to the token, and the hourly delete does not see it. A destroy removes only its KV entry. The follow of the zone after a migration export ends at once, because the account of the hosting provider does not hold the zone.