Skip to content

Create a blank site or duplicate from a source

POST
/wp-json/wp2/site-manager/v1/sites
curl --request POST \
--url https://example.com/wp-json/wp2/site-manager/v1/sites \
--header 'Content-Type: application/json' \
--header 'X-WP-Nonce: <X-WP-Nonce>' \
--data '{ "title": "Training project", "slug": "training", "description": "Course sandbox", "status_id": 12, "category_ids": [ 3 ], "user_id": 1 }'

Creates a site in the current network. This is not network creation (see Multi Network).

    Without `source_blog_id` (or 0) — synchronous `wpmu_create_blog` of a blank site and 201 `SiteDto`. `title` and `slug` are required after trim, otherwise 400 `missing_fields`. The slug is normalized; a free domain/path is chosen with auto_increment.

    If `source_blog_id` > 0 — enqueue `site_manager.create_from_source` and 202 with `poll_url` / `events_url` (Location header, Retry-After=2). Source not in the current network — 404 `site_not_in_network`.

    Requires `create_sites` or `manage_network`. A different `user_id` — super admin only (403 `forbidden_user`). Multisite only.
Media typeapplication/json
object
title
required

New site title (blogname / wpmu_create_blog argument). Required; empty after trim — 400 missing_fields.

string
slug
required

Address slug. Required. PHP normalizes and, for a blank site, picks a free domain/path (auto_increment). Empty after trim — 400 missing_fields.

string
description

Optional blogdescription; after create it is applied via SiteMetadataPatch.

string
status_id

Optional Site Manager status ID; after wpmu_create_blog it is written to site meta. Unknown term — 400 invalid_term.

integer
category_ids

Optional category IDs; empty or omitted array — no categories assigned.

Array<integer>
user_id

Owner/admin of the new site for wpmu_create_blog. Optional: defaults to the current REST user. Only a super admin may assign another id, otherwise 403 forbidden_user. user_id ≤ 0 after resolve — 401 missing_user.

integer
source_blog_id

If > 0, PHP does not create the site synchronously: it enqueues site_manager.create_from_source and returns 202. The source must be in the current network, otherwise 404 site_not_in_network. Without the field or 0 — blank site and 201 SiteDto.

integer
Example
{
"title": "Training project",
"slug": "training",
"description": "Course sandbox",
"status_id": 12,
"category_ids": [
3
],
"user_id": 1
}

Blank site created. Body is SiteMetadata::to_array.

Media typeapplication/json
object
blog_id

Site ID in the current network (WP_Site->blog_id). Path {blog_id} on the same operations is the same number.

integer
network_id

ID of the network the site belongs to (WP_Site->site_id). Not blog_id and not CreateNetworkRequest.domain.

integer
title

Site title — blog option blogname (get_option on this site). Not the network option site_name.

string
slug

Site address slug. On SUBDOMAIN_INSTALL — subdomain relative to the network domain; otherwise the WP_Site->path segment without slashes. PHP derives it in SiteSlugService::derive_slug_from_site.

string
domain

Site host in the blogs table (WP_Site->domain), without an https:// scheme.

string
path

Site path in the blogs table (WP_Site->path) with slashes (often / on subdomains or /training/ on subdirectory). Not a Local Sync file path.

string
description

Site tagline — blog option blogdescription.

string
status_id

Site Manager status taxonomy term ID (site meta wp2platform_site_status). null if unset or id=0.

integer
nullable
status

Expanded Site Manager status term. null if unset. Not the WP_Site->public flag.

object
id

Status term_id.

integer
name

Status display name.

string
slug

Status term slug (sanitize_title).

string
category_ids

Site Manager category term IDs (site meta wp2platform_site_categories).

Array<integer>
categories

Expanded site categories (id, name, slug). PHP does not include parent inside SiteDto.

Array<object>
object
id

Site category term_id (Site Manager taxonomy, not post categories).

integer
name

Term display name. In the list PHP may rewrite default labels via translate_default_term_label.

string
slug

Category term slug (sanitize_title on write).

string
parent

Parent term_id (WP_Term->parent); 0 — root. PHP omits this field in SiteDto.categories.

integer
site_url

Public site URL (get_site_url(blog_id)).

string format: uri
admin_url

This site’s wp-admin URL (get_admin_url(blog_id)), not /wp-admin/network/.

string format: uri
registered

Blog registration date (WP_Site->registered), as core stores it.

string format: date-time
last_updated

Blog last-updated date (WP_Site->last_updated).

string format: date-time
public

WP_Site->public flag (1 — public/active, 0 — unpublished). Not a Site Manager status term.

integer
archived

WP_Site->archived flag (1 — archived, 0 — not).

integer
deleted

WP_Site->deleted flag in the blogs table (core marked the site deleted). Not the result of this API’s DELETE.

integer
spam

WP_Site->spam flag (1 — marked as spam).

integer
mature

WP_Site->mature flag.

integer
is_main_site

True if this is the network main site (is_main_site(blog_id)). It cannot be deleted, archived, deactivated, or have its slug changed via this API (main_site_forbidden / main_site_slug_forbidden).

boolean
is_template

True if site meta wp2platform_site_manager_is_template is 1 (duplication template site).

boolean
user_count

User count on the site (count_users()['total_users']). PHP adds the field only if query include contains stats; otherwise the key is absent.

integer
Example
{
"blog_id": 5,
"network_id": 1,
"title": "Training project",
"slug": "training",
"domain": "training.example.com",
"path": "/",
"description": "Course sandbox",
"status_id": 12,
"site_url": "https://training.example.com/",
"admin_url": "https://training.example.com/wp-admin/",
"public": 1,
"archived": 0,
"deleted": 0,
"spam": 0,
"mature": 0,
"is_main_site": false,
"is_template": false
}

Duplicate job accepted (source_blog_id). Location is poll_url, Retry-After=2.

Media typeapplication/json
object
job_id

PlatformJob UUID. The same id in GET /jobs/{job_id} and SSE /jobs/{job_id}/events.

string format: uuid
status

Job status at the 202 moment (usually pending right after enqueue).

string
type

Site Manager job type. Duplicate create — site_manager.create_from_source; bulk — site_manager.bulk.

string
poll_url

JSON poll URL (rest_url(…/jobs/{job_id})). Also sent as Location; Retry-After=2.

string format: uri
events_url

SSE progress URL (…/jobs/{job_id}/events), not the JSON poll.

string format: uri
Example
{
"job_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "pending",
"type": "site_manager.create_from_source",
"poll_url": "https://example.com/wp-json/wp2/site-manager/v1/jobs/7c9e6679-7425-40de-944b-e07fc1f90ae7",
"events_url": "https://example.com/wp-json/wp2/site-manager/v1/jobs/7c9e6679-7425-40de-944b-e07fc1f90ae7/events"
}
Location
string

GET URL for job state (…/jobs/{job_id}).

Retry-After
integer

Seconds until the next poll (PHP sets 2).

Missing_fields — no title or slug; not_multisite; invalid_term — unknown status/category after create.

Media typeapplication/json
object
code

Machine-readable error or event code.

string
message

Message text (log, PIN, or password reset).

string
data

Extra WP_Error or license payload.

object
Examplegenerated
{
"code": "example",
"message": "example",
"data": {}
}

Not authenticated. DualRest: rest_forbidden (status 401) when there is no cookie session. Or missing_user — no valid site owner.

Media typeapplication/json
object
code

Machine-readable error or event code.

string
message

Message text (log, PIN, or password reset).

string
data

Extra WP_Error or license payload.

object
Examplegenerated
{
"code": "example",
"message": "example",
"data": {}
}

Missing create_sites/manage_network, invalid REST nonce, or forbidden_user (only a super admin may set user_id).

Media typeapplication/json
object
code

Machine-readable error or event code.

string
message

Message text (log, PIN, or password reset).

string
data

Extra WP_Error or license payload.

object
Examplegenerated
{
"code": "example",
"message": "example",
"data": {}
}

Site_not_in_network — source_blog_id is not in the current network.

Media typeapplication/json
object
code

Machine-readable error or event code.

string
message

Message text (log, PIN, or password reset).

string
data

Extra WP_Error or license payload.

object
Examplegenerated
{
"code": "example",
"message": "example",
"data": {}
}

WP_Error from wpmu_create_blog without HTTP-status in data (often a taken domain). WordPress REST surfaces these as 500.

Media typeapplication/json
object
code

Machine-readable error or event code.

string
message

Message text (log, PIN, or password reset).

string
data

Extra WP_Error or license payload.

object
Examplegenerated
{
"code": "example",
"message": "example",
"data": {}
}