hosting

POST /v1/site-imports

Restore a site from a web archive, or migrate one in.

All hosting endpoints

Authentication

Send an API key as a bearer token. The key must carry the hosting.import.manage permission; a key without it is refused with 403, not 404.

This endpoint takes no organisation id. Your key already identifies the organisation it belongs to, and the response is scoped to it.

Try it

Replace anything in angle brackets with your own values, and the key placeholder with a key from your dashboard.

curl -X POST https://api.zinndigital.com/v1/site-imports \
  -H "Authorization: Bearer zdk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "source": <SiteImportSource>, "output": <SiteImportOutput>, "source_domain": <string> }'

Signed in? The API console in your dashboard fills in your real organisation id and your own key, and runs the request against the live API so you can see the actual response. Open this endpoint in the API console

Details

Starts a durable, resumable import workflow and returns the new job state, never a bare acknowledgement. A Wayback restore **must** name `snapshot_day`: the most recent capture of an expired domain is usually a parking page, so there is no safe default (422 without it). The output is either a static HTML document root or a WordPress import — both are supported and neither is a default. A site with an import already running returns 409. ⚠️ `ownership_attested` is recorded, not enforced: the platform's obligation here is to know who requested what. Requires `hosting.import.manage`.

Parameters

NameTypeRequiredWhat it is
Idempotency-Key (header)stringNoClient-generated key that makes an unsafe request replay-safe: the server stores the first response and returns it verbatim for repeats.

Request body

NameTypeRequiredWhat it is
sourceSiteImportSourceYesWhere the content comes from. ⛔ Deliberately not the same enum as a site's `source`: that one records what a site was built from, for ever, while this selects a fetch strategy a…
outputSiteImportOutputYesWhat the import produces. Both are supported; neither is a default.
source_domainstringYesThe domain whose archive to restore, or — for `archive_url` — the URL of the archive to migrate in.
target_domainstringNoThe domain the result is served on. Defaults to `source_domain`.
site_idUuidNoRestore into an existing site. Omit to produce a bundle and choose the target later.
snapshot_daystringNoWhich day's snapshot to restore. **Required for `wayback`** — the most recent capture of an expired domain is usually a parking page, so there is no safe default.
crawl_depthintegerNo
request_budgetintegerNoThe hard ceiling on requests this job may make against the source. Bounded per job so one 400,000-capture domain cannot consume the platform's whole archive allowance.
ownership_attestedbooleanNo⚠️ The customer's assertion that they control the domain, recorded because it cannot be reconstructed afterwards. It is **not** a legal control and is not presented as one.

Response

NameTypeRequiredWhat it is
idUuidYesUUIDv7 identifier — sortable by creation time (docs/02 §8).
site_idstringNoThe site this import lands on, or empty. Empty is a real outcome, not an error: an aged-domain restore produces a downloadable bundle and the customer picks the target afterwards.
sourceSiteImportSourceYesWhere the content comes from. ⛔ Deliberately not the same enum as a site's `source`: that one records what a site was built from, for ever, while this selects a fetch strategy a…
outputSiteImportOutputYesWhat the import produces. Both are supported; neither is a default.
statusSiteImportStatusYes`fetching` and `building` are separate states rather than one `running` because they fail for opposite reasons and the customer's next action differs — a stall in `fetching` is…
status_reasonstringNoA machine code the dashboard renders through its own catalogue — never an English sentence composed by the engine, which could not be shown to the other 57 locales.
source_domainstringYes
target_domainstringNo
snapshot_daystringNo
crawl_depthintegerNo
urls_totalintegerYes
urls_doneintegerYes
urls_failedintegerYes
progress_percentintegerYesFor a progress bar only. The per-URL counters are the primary display, because a percentage cannot say *which* step.
requests_spentintegerYes
request_budgetintegerYes
budget_exhaustedbooleanYesTrue when the job stopped because it ran out of requests. Surfaced deliberately — it otherwise looks identical to a job that finished, and the customer's next action depends on…
bundle_bytesintegerNo
created_atstringYes
started_atstringNo
finished_atstringNo
can_cancelbooleanYes

Errors this endpoint can return

401 · 403 · 404 · 409 · 422 · 429 · 503