Prepare or check CDN setup
POST /api/v2/cdn/zones/{id}
Runs one CDN setup step for a zone. Send one action and no other fields, except hostnames with prepare_partial. The domain must be active. Otherwise the call returns 404 cdn_zone_not_found. Every step returns the full zone, as from GET /api/v2/cdn/zones/{id}.
There are two ways to put a domain behind the CDN.
Move to Hostup’s nameservers. While actions.canPrepareNameservers is allowed, Hostup’s nameservers do not answer for the zone yet. A nameserver change at that point takes the domain offline. Send prepare_nameservers first. It queues the zone on Hostup’s nameservers and never changes the domain’s nameservers. Publication runs in the background. Read GET /api/v2/cdn/zones/{id} every few seconds until actions.canPrepareNameservers.allowed is false. Then switch the domain to setup.expectedNameservers. For a domain registered with Hostup, use POST /api/v2/domains/{id}/nameservers. For a domain registered elsewhere, set them at that registrar. Repeating the step is safe. A zone that uses external DNS setup returns 409.
Keep your external DNS. prepare_partial prepares the zone and returns an ownership TXT record in setup.verification and one CNAME or ALIAS record per hostname in setup.routing. To choose the hostnames, send hostnames: 1 to 50 names at or below the zone, without wildcards, URL schemes or paths. It changes nothing at your DNS provider. Publish exactly the returned records there. An apex record needs ALIAS or CNAME flattening support at that provider. Then send check_partial to recheck verification, routing and the certificate.
HTTP 200 means the step ran, not that the CDN is live. pending_verification, pending_routing and pending_certificate name the work still open. Only setup.status: active means traffic and HTTPS run through the CDN. Settings such as proxying and security are changed with PATCH /api/v2/cdn/zones/{id}.
Authentication: An API key with the scopes write:cdn and write:domains. About keys and scopes
Request
Path parameters
idstringrequiredGET /api/v2/cdn/zones.Body
actionstringrequiredprepare_partial.hostnamesarray of stringwww. Send 1 to 50 entries. Duplicates are merged. Wildcards, URL schemes and paths are rejected. Case, IDNs and a trailing dot are normalized. The current origins are read from your DNS records.Example
curl -X POST "https://cloud.hostup.se/api/v2/cdn/zones/cdn_01hxa3b4c5d6e7f8g9h0j1k2m3" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "prepare_partial" }'curl -X POST "https://cloud.hostup.se/api/v2/cdn/zones/cdn_01hxa3b4c5d6e7f8g9h0j1k2m3" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "prepare_partial", "hostnames": [ "www.example.com" ] }'curl -X POST "https://cloud.hostup.se/api/v2/cdn/zones/cdn_01hxa3b4c5d6e7f8g9h0j1k2m3" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "check_partial" }'curl -X POST "https://cloud.hostup.se/api/v2/cdn/zones/cdn_01hxa3b4c5d6e7f8g9h0j1k2m3" \ -H "Authorization: Bearer $HOSTUP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "prepare_nameservers" }'Responses
200 The zone after the step. A pending setup state or an open canPrepareNameservers gate is a normal result.
{ "id": "cdn_01hxa3b4c5d6e7f8g9h0j1k2m3", "domain": "example.com", "domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3", "status": "pending", "reason": "Add the verification TXT record at your DNS provider.", "proxied": false, "securityLevel": "medium", "ssl": { "status": "pending", "expiresAt": null }, "ruleCount": 2, "security": { "level": "medium", "sslMode": "full", "alwaysUseHttps": true, "minTlsVersion": "1.2", "botProtection": true, "blockBadCrawlers": true, "blockBadBots": true, "wpLoginProtection": true, "wpAdminChallenge": true, "blockedCrawlers": [] }, "performance": { "earlyHints": true, "alwaysOnline": true }, "cache": { "purgeCache": false }, "waf": { "skipEnabled": false, "challengeGeoEnabled": true, "visitorProfileMode": "challenge", "ipAllowlist": [ "198.51.100.10" ], "uaAllowlist": [ "ExampleMonitor/1.0" ], "pathAllowlist": [ "/health" ], "uaBlocklist": [], "ipBlocklist": [] }, "geoRestriction": { "enabled": true, "whitelistCountries": [ "SE" ], "mode": "whitelist", "standardCountries": [], "additionalCountries": [ "SE" ], "combinedCountries": [ "SE" ] }, "activity": { "lastChangeDetectedAt": "2026-04-27T12:00:00.000Z", "lastCheckedAt": "2026-04-27T12:00:00.000Z", "settingsUpdatedAt": "2026-04-27T12:00:00.000Z" }, "actions": { "canEnableProxy": { "allowed": false, "reason": "Add the verification TXT record at your DNS provider." }, "canIssueCertificate": { "allowed": true, "reason": null }, "canChangeMode": { "allowed": true, "reason": null }, "canActivate": { "allowed": false, "reason": "Add the verification TXT record at your DNS provider." }, "canDeactivate": { "allowed": false, "reason": "CDN is not active for this domain." }, "canPreparePartial": { "allowed": false, "reason": "External DNS setup is already prepared. Check its current status." }, "canCheckPartial": { "allowed": true, "reason": null }, "canPrepareNameservers": { "allowed": false, "reason": "External DNS setup is prepared for this domain." } }, "records": null, "setup": { "mode": "partial", "status": "pending_verification", "reason": "Add the verification TXT record at your DNS provider.", "verification": { "type": "TXT", "name": "cdn-verification.example.com", "value": "example-verification-value", "status": "pending" }, "routing": [ { "hostname": "www.example.com", "currentOrigin": [ { "type": "A", "value": "198.51.100.10" } ], "record": { "type": "CNAME", "name": "www.example.com", "value": "www.example.com.cdn.example.net" }, "status": "pending", "reason": null } ], "certificate": { "status": "pending", "hosts": [] }, "checkedAt": "2026-04-27T12:00:00.000Z", "currentNameservers": [ "ns1.example.net", "ns2.example.net" ], "expectedNameservers": [ "primary.ns.hostup.se", "secondary.ns.hostup.se" ], "registrarRelation": "hostup", "nameserverManagementUrl": "/domains/example.com?tab=nameservers" }}{ "id": "cdn_01hxa3b4c5d6e7f8g9h0j1k2m3", "domain": "example.com", "domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3", "status": "active", "reason": null, "proxied": true, "securityLevel": "medium", "ssl": { "status": "active", "expiresAt": "2026-08-01T00:00:00.000Z" }, "ruleCount": 2, "security": { "level": "medium", "sslMode": "full", "alwaysUseHttps": true, "minTlsVersion": "1.2", "botProtection": true, "blockBadCrawlers": true, "blockBadBots": true, "wpLoginProtection": true, "wpAdminChallenge": true, "blockedCrawlers": [] }, "performance": { "earlyHints": true, "alwaysOnline": true }, "cache": { "purgeCache": false }, "waf": { "skipEnabled": false, "challengeGeoEnabled": true, "visitorProfileMode": "challenge", "ipAllowlist": [ "198.51.100.10" ], "uaAllowlist": [ "ExampleMonitor/1.0" ], "pathAllowlist": [ "/health" ], "uaBlocklist": [], "ipBlocklist": [] }, "geoRestriction": { "enabled": true, "whitelistCountries": [ "SE" ], "mode": "whitelist", "standardCountries": [], "additionalCountries": [ "SE" ], "combinedCountries": [ "SE" ] }, "activity": { "lastChangeDetectedAt": "2026-04-27T12:00:00.000Z", "lastCheckedAt": "2026-04-27T12:00:00.000Z", "settingsUpdatedAt": "2026-04-27T12:00:00.000Z" }, "actions": { "canEnableProxy": { "allowed": true, "reason": null }, "canIssueCertificate": { "allowed": true, "reason": null }, "canChangeMode": { "allowed": true, "reason": null }, "canActivate": { "allowed": false, "reason": "CDN is already active for this domain." }, "canDeactivate": { "allowed": true, "reason": null }, "canPreparePartial": { "allowed": false, "reason": "External DNS setup is already prepared. Check its current status." }, "canCheckPartial": { "allowed": true, "reason": null }, "canPrepareNameservers": { "allowed": false, "reason": "External DNS setup is prepared for this domain." } }, "records": null, "setup": { "mode": "partial", "status": "active", "reason": null, "verification": { "type": "TXT", "name": "cdn-verification.example.com", "value": "example-verification-value", "status": "verified" }, "routing": [ { "hostname": "www.example.com", "currentOrigin": [ { "type": "A", "value": "198.51.100.10" } ], "record": { "type": "CNAME", "name": "www.example.com", "value": "www.example.com.cdn.example.net" }, "status": "verified", "reason": null } ], "certificate": { "status": "active", "hosts": [ "www.example.com" ] }, "checkedAt": "2026-04-27T12:00:00.000Z", "currentNameservers": [ "ns1.example.net", "ns2.example.net" ], "expectedNameservers": [ "primary.ns.hostup.se", "secondary.ns.hostup.se" ], "registrarRelation": "hostup", "nameserverManagementUrl": "/domains/example.com?tab=nameservers" }}{ "id": "cdn_01hxa3b4c5d6e7f8g9h0j1k2m3", "domain": "example.com", "domainId": "dom_01hxa3b4c5d6e7f8g9h0j1k2m3", "status": "misconfigured", "reason": "Nameservers are not pointing to Hostup.", "proxied": false, "securityLevel": "medium", "ssl": { "status": "pending", "expiresAt": null }, "ruleCount": 2, "security": { "level": "medium", "sslMode": "full", "alwaysUseHttps": true, "minTlsVersion": "1.2", "botProtection": true, "blockBadCrawlers": true, "blockBadBots": true, "wpLoginProtection": true, "wpAdminChallenge": true, "blockedCrawlers": [] }, "performance": { "earlyHints": true, "alwaysOnline": true }, "cache": { "purgeCache": false }, "waf": { "skipEnabled": false, "challengeGeoEnabled": true, "visitorProfileMode": "challenge", "ipAllowlist": [ "198.51.100.10" ], "uaAllowlist": [ "ExampleMonitor/1.0" ], "pathAllowlist": [ "/health" ], "uaBlocklist": [], "ipBlocklist": [] }, "geoRestriction": { "enabled": true, "whitelistCountries": [ "SE" ], "mode": "whitelist", "standardCountries": [], "additionalCountries": [ "SE" ], "combinedCountries": [ "SE" ] }, "activity": { "lastChangeDetectedAt": "2026-04-27T12:00:00.000Z", "lastCheckedAt": "2026-04-27T12:00:00.000Z", "settingsUpdatedAt": "2026-04-27T12:00:00.000Z" }, "actions": { "canEnableProxy": { "allowed": false, "reason": "Nameservers are not pointing to Hostup." }, "canIssueCertificate": { "allowed": true, "reason": null }, "canChangeMode": { "allowed": true, "reason": null }, "canActivate": { "allowed": false, "reason": "Nameservers are not pointing to Hostup." }, "canDeactivate": { "allowed": false, "reason": "CDN is not active for this domain." }, "canPreparePartial": { "allowed": true, "reason": null }, "canCheckPartial": { "allowed": false, "reason": "Prepare external DNS setup first." }, "canPrepareNameservers": { "allowed": false, "reason": "Hostup's nameservers already answer for this domain." } }, "records": null, "setup": { "mode": "full", "status": "not_configured", "reason": null, "verification": null, "routing": [], "certificate": { "status": "unknown", "hosts": [] }, "checkedAt": null, "currentNameservers": [ "ns1.example.net", "ns2.example.net" ], "expectedNameservers": [ "primary.ns.hostup.se", "secondary.ns.hostup.se" ], "registrarRelation": "hostup", "nameserverManagementUrl": "/domains/example.com?tab=nameservers" }}Response fields
setupobjectstatus and reason as returned. A prepared partial setup is not active until the verification, the routing and the HTTPS certificate are confirmed. Copy the DNS names and values as returned. Don’t build them yourself.modestringfull: the domain uses Hostup’s nameservers. partial: your own DNS stays authoritative and chosen hostnames are routed through the CDN. currentNameservers shows the nameservers seen now. One of full, partial, unknown.statusstringpending_… status names the next setup step. unavailable means the state could not be read. It does not mean that DNS or the certificate is wrong. One of not_configured, pending_verification, pending_routing, pending_certificate, active, unavailable.reasonstring or nullverificationobject or nullroutingarray of objectstatus: unsupported is not set up.hostnamestringcurrentOriginarray of objecttypestringA, AAAA, CNAME.valuestringrecordobject or nullALIAS only when your provider supports a flattened alias at the zone apex. Don’t replace an apex with a plain CNAME when that conflicts with the other records at the apex.statusstringpending, verified, unsupported, unknown.reasonstring or nullcertificateobjectstatusstringactive, pending, error, unknown.hostsarray of stringcheckedAtstring or nullcurrentNameserversarray of stringexpectedNameserversarray of stringregistrarRelationstringhostup, external, unknown.nameserverManagementUrlstring or nullexpectedNameservers at your registrar.idstring or nullcdn_…). Set even before the CDN is set up. Null only when the zone cannot be identified.domainstringdomainIdstring or nulldom_…) for a domain registered with Hostup. null for a DNS-only zone.statusstringactive, pending, misconfigured, missing, disabled.reasonstring or nullproxiedbooleansecurityLevelstringoff, low, medium, high.sslobjectstatusstringactive, pending, error.expiresAtstring or nullruleCountintegersecurityobjectlevelstringoff, low, medium, high.sslModestringoff, flexible, full, strict.alwaysUseHttpsbooleanminTlsVersionstring1.0, 1.1, 1.2, 1.3.botProtectionbooleanblockBadCrawlersbooleanblockBadBotsbooleanwpLoginProtectionbooleanwpAdminChallengebooleanblockedCrawlersarray of stringbaiduspider, gptbot, claudebot, ahrefsbot. Lowercase IDs only. The crawler catalogue has the full list. Empty unless crawlers were chosen. Separate from blockBadCrawlers, the general check for bad crawlers.performanceobjectearlyHintsbooleanalwaysOnlinebooleancacheobjectpurgeCachebooleanwafobjectskipEnabledbooleanchallengeGeoEnabledbooleanvisitorProfileModestringchallenge: visitors from outside the allowed countries must pass a challenge. block: they are blocked. off: the visitor profile does nothing. One of off, challenge, block.ipAllowlistarray of stringuaAllowlistarray of stringpathAllowlistarray of stringuaBlocklistarray of stringMozilla, Chrome or bot, are rejected with invalid_request. They would block ordinary visitors.ipBlocklistarray of stringgeoRestrictionobjectenabledbooleanwhitelistCountriesarray of stringcombinedCountries instead.modestringoff: no country limit. whitelist: only the countries in combinedCountries are allowed. One of off, whitelist.standardCountriesarray of stringadditionalCountriesarray of stringstandardCountries.combinedCountriesarray of stringstandardCountries plus additionalCountries.activityobjectlastChangeDetectedAtstring or nulllastCheckedAtstring or nullsettingsUpdatedAtstring or nullactionsobjectcanEnableProxyobjectallowedbooleanreasonstring or nullcodestring or nullcanIssueCertificateobjectallowedbooleanreasonstring or nullcodestring or nullcanChangeModeobjectallowedbooleanreasonstring or nullcodestring or nullcanActivateobjectallowedbooleanreasonstring or nullcodestring or nullcanDeactivateobjectallowedbooleanreasonstring or nullcodestring or nullcanPreparePartialobjectallowedbooleanreasonstring or nullcodestring or nullcanCheckPartialobjectallowedbooleanreasonstring or nullcodestring or nullcanPrepareNameserversobjectprepare_nameservers to POST /api/v2/cdn/zones/{id}. Read the zone until this gate closes. Only then change the nameservers. Not allowed when the zone is already published, when it uses your own DNS or when readiness was not checked.allowedbooleanreasonstring or nullcodestring or nullrecordsarray of any or nullnull unless you send includeRecords=true.Errors
Errors are Problem Details with a stable code. Branch on the code, not on the text.
| 400 | The action is missing or unknown, hostnames is invalid or the body has other fields. |
| 401 | Unauthorized. Authentication is required. |
| 403 | Forbidden. The caller lacks a required scope or does not own the resource. |
| 404 | No CDN zone with this ID on the account. The domain may also not be active. |
| 409 | prepare_nameservers does not apply. Either the zone uses external DNS setup or another account holds the domain’s DNS zone. |
| 429 | Rate limited. Retry after the limit resets. 429 responses include Retry-After seconds plus X-RateLimit-* headers. |
| 500 | Internal error. Retry later or contact support if the issue persists. |
| 502 | The setup step returned no usable result. Read the zone before you retry. |
| 503 | The setup step cannot run right now. Try again later. |