Skip to content

Transfer a domain to Hostup from another registrar

7 steps · Checked against the API code 3 November 2026

A guide does one task from start to finish. Every endpoint, with all its fields and cases, is in the API reference.

A transfer moves the registration of a domain you already own from its current registrar to Hostup. You order it like any other product and pay the invoice. Then the registry takes over. The API tells you where the transfer stands and what to fix when the registry refuses it.

  • An API key with these scopes (Keys and scopes):
    • write:orders to order the transfer.
    • read:domains to check the domain and follow the transfer.
    • write:domains to retry the transfer or save a new code.
    • write:billing to pay the invoice through the API. Leave it out if the account holder pays in the client area.
  • The requests read the key from $HOSTUP_API_KEY. The Quickstart shows how to set it.
  • At the current registrar:
    • Turn off the transfer lock on the domain.
    • Get the transfer code. Registrars also call it the EPP code, auth code or authorization code.
    • For .com and many other generic extensions, check that the domain was registered more than 60 days ago. A younger domain cannot be transferred yet.
  • An account profile with the holder details the domain extension asks for. Step 1 shows which.
  • uuidgen for the order’s retry key in step 3. Any other way to make a random UUID works too.

1. Check that the domain can be transferred

Section titled “1. Check that the domain can be transferred”

Ask for the domain’s availability with GET /api/v2/domains/availability. Put your own domain in place of example.com here and in the requests below.

GET /api/v2/domains/availability
curl "https://cloud.hostup.se/api/v2/domains/availability?name=example.com" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"name": "example.com",
"state": "unavailable",
"actions": {
"canTransfer": { "allowed": true, "reason": null },
"…": "…"
},
"premium": false,
"requiresRegistrarFeeAcceptance": false,
"eppRequired": true,
"supportedTransferYears": [1],
"…": "…"
}

Go on when actions.canTransfer.allowed is true. It is false when the name is free to register or already at Hostup. It is also false when the check did not finish. It does not look at the lock or the code. The registry checks those later.

  • eppRequired: false when the extension uses no code. Leave eppCode out of the order then. A .uk transfer never uses one.
  • supportedTransferYears: the values you can send as years.
  • premium or requiresRegistrarFeeAcceptance is true: the registrar charges more than usual for this domain. The order charges that price. The preview in step 2 shows it.
  • registryRequirements.transfer: what the order must carry for this extension, for example a phone number and an identity number for .se. The order takes them from the account profile.

2. Choose the nameservers and preview the order

Section titled “2. Choose the nameservers and preview the order”

Always send nameserverMode in the order item. Without it, the account’s setting for transfers in the client area decides where the domain ends up.

  • preserve_current keeps the nameservers the domain has today. Your current DNS provider keeps answering for the domain. Nothing changes for the website or e-mail. The order reads the current nameservers when you send it.
  • hostup moves the domain to Hostup’s nameservers, primary.ns.hostup.se and secondary.ns.hostup.se. The order creates a DNS zone for the domain at Hostup. Add "copyExistingDns": true to copy the records found on the current nameservers into it. copyExistingDnsScope limits the copy to website or email records.
  • custom with at least two nameservers sends a new set.

POST /api/v2/orders/preview takes the same body as the order. It creates nothing. eppCode is the transfer code from the current registrar.

POST /api/v2/orders/preview
curl -X POST "https://cloud.hostup.se/api/v2/orders/preview" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "domain",
"action": "transfer",
"domainName": "example.com",
"years": 1,
"eppCode": "Xy7-kP2q-9LmR",
"nameserverMode": "preserve_current"
}
]
}'
Response 200
{
"items": [
{
"domainName": "example.com",
"type": "domain",
"nameserverMode": "preserve_current",
"…": "…"
}
],
"total": 186.25,
"currencyCode": "SEK",
"…": "…"
}

total is the price with tax. The amount here is an example. items[0].nameserverMode is the mode the order will apply. Read it when you left the mode out. The preview does not check the code.

Make a new attemptKey for this order and keep it in a variable:

export ATTEMPT_KEY="$(uuidgen)"

Today the key is not tied to your account. If anyone sent the same key in the last hour, you get that earlier order back with 200. Nothing is ordered for you. Never use a key copied from a page or an example.

Send the same item to POST /api/v2/orders. The call waits until the order exists and answers 201.

POST /api/v2/orders
curl -X POST "https://cloud.hostup.se/api/v2/orders" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptKey": "'$ATTEMPT_KEY'",
"paymentMethod": "card",
"items": [
{
"type": "domain",
"action": "transfer",
"domainName": "example.com",
"years": 1,
"eppCode": "Xy7-kP2q-9LmR",
"nameserverMode": "preserve_current"
}
]
}'
Response 201
{
"id": "ord_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "pending",
"invoiceId": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"invoice": {
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "unpaid",
"paymentUrl": "/billing?invoice=202600001",
"…": "…"
},
"paymentStatus": {
"status": "unpaid",
"reason": "Invoice has not been paid yet."
},
"actions": {
"canCancel": { "allowed": true, "reason": null },
"…": "…"
},
"…": "…"
}

Keep the order ID and the invoice ID:

export ORDER_ID="ord_…" # id above
export INVOICE_ID="inv_…" # invoiceId above

In the body, '$ATTEMPT_KEY' steps out of the single quotes. The shell then puts the key in. After a timeout or a lost answer, send the same request with the same key. Sending a change twice explains the key.

The order creates an invoice. id is the order. invoiceId is the invoice you pay in step 4. paymentMethod is a hint for how the invoice will be paid. It charges nothing.

A transfer ordered with an API key does not start before its invoice is paid. When paymentStatus.status in the order answer is already paid, go to step 5.

Send {} to POST /api/v2/billing/invoices/{id}/actions/pay to charge the primary saved card.

POST /api/v2/billing/invoices/{id}/actions/pay
curl -X POST "https://cloud.hostup.se/api/v2/billing/invoices/$INVOICE_ID/actions/pay" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"id": "inv_01hxa3b4c5d6e7f8g9h0j1k2m3",
"status": "paid",
"totals": {
"outstanding": 0,
"…": "…"
},
"…": "…"
}
  • Without a saved card the call answers 409 payment_method_invalid.
  • To pay from account credit, send an amount to POST /api/v2/billing/invoices/{id}/actions/apply-credit. It can be no more than the account’s credit and no more than the invoice still owes. When the credit covers only part of the invoice, the rest stays unpaid.
  • The account holder can pay in the client area: https://cloud.hostup.se followed by invoice.paymentUrl from the order, for example https://cloud.hostup.se/billing?invoice=202600001.

Pay an invoice covers every way to pay.

Cancel the order with POST /api/v2/orders/{id}/actions/cancel and $ORDER_ID while actions.canCancel.allowed on the order is true. That also cancels its unpaid invoice.

The order answer has no domain ID. The domain is in your domain list from the moment the order exists. Look it up by name with GET /api/v2/domains. Every domain endpoint takes its dom_… ID. IDs explains the prefix.

GET /api/v2/domains
curl "https://cloud.hostup.se/api/v2/domains?name=example.com" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"id": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3",
"name": "example.com",
"serviceStatus": "pending",
"awaitingPayment": false,
"lifecycle": {
"type": "transfer",
"transferInProgress": true,
"…": "…"
},
"…": "…"
}
],
"…": "…"
}
export DOMAIN_ID="dom_…" # data[0].id above

awaitingPayment is true while the invoice is unpaid. transferInProgress turns true when the transfer has reached the registry.

GET /api/v2/domains/{id}/lifecycle says where the transfer stands. There is no job to poll. A transfer at the registry can take days. Poll a few times a day, not in a tight loop.

GET /api/v2/domains/{id}/lifecycle
curl "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/lifecycle" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"actionRequired": false,
"transferFailure": null,
"transferStatus": {
"kind": "pending_transfer_in_progress",
"reason": "Transfer is being processed at the registry.",
"actions": {
"canRetryTransfer": {
"allowed": false,
"reason": "Transfer is already in progress at the registry."
},
"…": "…"
},
"progress": { "daysPassed": 1, "daysRemaining": 4, "maxDays": 5, "percent": 20 },
"…": "…"
},
"…": "…"
}

transferStatus.kind is the state. reason says it in words. actionRequired is true when the registry refused the transfer for a reason someone has to fix. Find your kind in the table:

transferStatus.kindWhat to do
awaiting_paymentThe invoice is unpaid. Go back to step 4.
pending_transfer_in_progress, pending_transfer_genericThe registry has the transfer. Wait. progress estimates the approval window. For a generic extension such as .com it counts five days unless the registry states another window. progress is null when the window is not known.
registry_lockedThe transfer lock at the current registrar is still on. Turn it off there. Then retry without a new code.
registry_locked_and_epp_invalidThe transfer lock is still on and the code was wrong as well. Turn off the lock. Then retry with a new code.
too_new_for_transferThe domain was registered less than 60 days ago. Wait until the 60 days have passed. The API does not return the date when that is. The current registrar shows when the domain was registered. A retry before then answers 429 cooldown_active.
registry_suspendedThe registry has suspended the domain. The current registrar has to have the suspension lifted first.
transfer_error_known, transfer_failed_at_registryThe transfer stopped. transferFailure.category says why. epp_invalid means the code was wrong: retry with a new code. For other categories, transferFailure.reason says what to fix. When transferFailure.recovery is set, fill in its suggestedBody and send it with the retry.
pending_epp_fallbackThe transfer waits for a valid code. Retry with a new code.
transfer_not_at_registryThe registry no longer has the transfer. Retry without a new code.
registrar_push_request_required (.uk)No code is used. Retry without a new code.
registrar_push_required (.uk)Ask your current provider to set the tag in nextStep.registrarTag within nextStep.deadlineDays days.
activeThe transfer is done. Go to step 7.
expired, unknownThe domain is no longer a transfer in progress, for example because the order was cancelled. Nothing can be retried. A retry answers 409 transfer_not_in_progress.

When transferStatus.kind is active, the domain is registered through Hostup. Check its nameservers with GET /api/v2/domains/{id}/nameservers.

GET /api/v2/domains/{id}/nameservers
curl "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/nameservers" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"nameservers": ["ns1.example.net", "ns2.example.net"],
"actions": {
"canChangeNameservers": { "allowed": true, "reason": null }
},
"…": "…"
}

With preserve_current you see the nameservers the domain had before. With hostup you see primary.ns.hostup.se and secondary.ns.hostup.se. Nameservers cannot be changed while the transfer runs. canChangeNameservers is false until it is done. After that, Change a domain’s nameservers shows how to change them.

Do this when step 6 sends you here. Read the lifecycle again first (step 6). transferStatus.actions.canRetryTransfer.allowed must be true. When it is false, its reason says why a retry would not help now.

The retry itself does not check the invoice. Sent while the invoice is unpaid, it starts the transfer at the registry before payment. Retry only when canRetryTransfer allows it.

POST /api/v2/domains/{id}/actions/retry-transfer sends the transfer to the registry again. It answers when the registry has answered. A retry creates no new invoice. What you send depends on why the transfer stopped.

A 422 transfer_recovery_required answer means the registry wants more holder details. errors[].pointer names each one. Its recovery.suggestedBody is the body to fill in and send back. When the lifecycle shows transferFailure.recovery, its suggestedBody works the same way. Update the account profile and send "profileUpdated": true. Send details meant for the transfer itself in recoveryValues.

Get a newly issued code from the current registrar. Send it as eppCode. The call saves it before it sends the transfer.

POST /api/v2/domains/{id}/actions/retry-transfer
curl -X POST "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/actions/retry-transfer" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"eppCode": "Qw4-tR8n-2VbZ"}'
Response 200
{
"domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3",
"transferStatus": "pending_transfer_in_progress",
"reason": "Transfer is being processed at the registry.",
"actions": {
"canRetryTransfer": {
"allowed": false,
"reason": "Transfer is already in progress at the registry."
},
"…": "…"
},
"…": "…"
}

Here transferStatus is the kind as a string. The lifecycle has it as transferStatus.kind. Follow the transfer with step 6 again.

transferAttempts.currentCodeRejected in the lifecycle says whether the registry has already refused the saved code. Such a code is not sent again. Add "force": true only when you know the same code is valid now.

Send {} as the body. The saved code is sent again. A .uk transfer sends no code. Some registrars issue a new code when you turn off the lock. Then retry with the new code instead.

POST /api/v2/domains/{id}/actions/retry-transfer
curl -X POST "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/actions/retry-transfer" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Response 200
{
"domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3",
"transferStatus": "pending_transfer_in_progress",
"reason": "Transfer is being processed at the registry.",
"…": "…"
}

When canRetryTransfer is not allowed but canUpdateEpp is, save the code on its own with POST /api/v2/domains/{id}/actions/update-epp. That happens while the invoice is unpaid, for example.

POST /api/v2/domains/{id}/actions/update-epp
curl -X POST "https://cloud.hostup.se/api/v2/domains/$DOMAIN_ID/actions/update-epp" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"eppCode": "Qw4-tR8n-2VbZ"}'
Response 200
{
"domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3",
"eppCodePersisted": true,
"transferRetryReady": true,
"updatedAt": "2026-11-02T10:15:41.000Z"
}

This only saves the code. A later retry with {} sends the saved code. transferRetryReady is always true. It does not mean a retry is allowed now. Check canRetryTransfer in the lifecycle before you retry.

  • E-mails. The account holder gets an e-mail when the transfer starts at the registry and another when it is complete. For .se and .nu the start e-mail is skipped because those transfers finish quickly. When the registry refuses a transfer, the account holder can get an e-mail about that too. No transfer e-mail goes out when domain notifications are turned off for the account.
  • Money. The order creates one invoice. The transfer waits until it is paid. Retries cost nothing extra.
  • DNS. The domain ends up on the nameservers you chose in step 2. With hostup the DNS zone at Hostup becomes the one the internet reads.

Next: Point a domain at your server when the domain is on Hostup’s nameservers. For DNS somewhere else, change the nameservers.

  • 403 human_verification_required from the availability check: the key lacks read:domains. Without it the API treats the call as an anonymous visitor. Add the scope to the key.
  • 502 upstream_failed from the availability check: the check did not finish within about 20 seconds. Send it again.
  • 400 invalid_request from the order: something the extension needs is missing. errors[].pointer names it. /items/0/eppCode means the code is missing. A pointer under /clientData means the account profile lacks a holder detail. Add it to the profile or send it in clientData.
  • 422 invalid_request from the order, with the pointer /items/0/nameserverMode: preserve_current found no current nameservers on the domain. Choose another mode.
  • 502 upstream_failed from the order: preserve_current could not read the current nameservers. Nothing was ordered. Send the order again later or choose another mode.
  • 409 domain_conflict from the order: the domain is already at Hostup or already ordered. conflicts[].reasonCode says which, for example already_in_account. A name that is not registered anywhere answers 409 domain_not_available instead. Register it instead of transferring it.
  • 503 domain_verification_unavailable from the order: the check that the name is registered did not finish. Nothing was ordered. Wait a little. Then send the same body with the same attemptKey again.
  • 504 with the code TIMEOUT from the order: the API stopped waiting for the answer. The order may still be placed. Send the same body with the same attemptKey a little later. If the first request placed the order, you get that order back with 200.
  • 409 transfer_blocked from a retry: errors[].code names the blocker. registry_lock means unlock at the current registrar. epp_invalid and epp_code_unchanged mean get a new code. Retry when it is fixed. registrar_fee_confirmation and registrant_not_eligible cannot be fixed with a retry. Contact support with the requestId from the answer.
  • 422 transfer_recovery_required from a retry: the registry needs more holder details. See If the registry wants more details.