---
title: Register or update a WordPress site
url: https://developers.wpengine.com/docs/ai/api/reference/operations/registersite/
image: https://developers.wpengine.com/og/docs/ai/api/reference/operations/registersite.png
---

# Register or update a WordPress site

POST

/v1/sites

Select code sample

```php
<?php


$client = new \GuzzleHttp\Client();


$response = $client->request('POST', 'https://api.ai.wpengine.com/v1/sites', [
  'body' => '{ "site_url": "https://example.wpengine.com", "display_name": "My Blog", "project_id": "proj_01abc", "site_info": { "active_plugins": [ "example" ], "php_version": "8.2.0", "wp_version": "6.5.0", "active_theme": "twentytwentyfour", "locale": "en_US", "server_software": "Apache/2.4.57", "db_version": "8.0.33", "memory_limit": "256M", "timezone": "America/Chicago", "active_plugin_count": 12, "ssl_valid": true, "is_multisite": false, "wp_debug": false, "wpe_plugins": { "wpe-hub": "1.2.0" } } }',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);


echo $response->getBody();
```

- Current environment

Registers a WordPress site with the AI Gateway, or updates an existing registration for the same site. The caller must authenticate with a WP Engine oauth-ext access token obtained via Dynamic Client Registration (RFC 7591). The gateway returns a stable site_id for the logical site; client_id continues to identify the current OAuth registration. Re-registering the same OAuth client updates its metadata in place. Request body is capped at 64 KiB. Unknown JSON fields are rejected. Returns 409 if the token’s account_id differs from the account that originally registered the client_id (ownership conflict), or if an existing client moves to a site_url already owned by another site in the same account.

## Authorizations

- **[bearerAuth](https://developers.wpengine.com/docs/ai/api/reference/#bearerauth)**

## Request Bodyrequired

Media typeapplication/json

object

**site_url**

required

Fully-qualified URL of the WordPress site (scheme + host required). Longer than 2048 bytes is rejected with 400.

string format: uri

<= 2048 characters

### Example

```
https://example.wpengine.com
```

**display_name**

Human-readable name for the site, at most 255 characters (characters, not bytes — multi-byte names are measured the same way). Optional; defaults to site_url, truncated to fit.

string

<= 255 characters

### Example

```
My Blog
```

**project_id**

WP Engine project to associate the site with. Optional — omit when the site is not scoped to a project.

string

<= 64 characters

### Example

```
proj_01abc
```

**site_info**

WordPress environment metadata collected by the plugin at registration time. All fields are optional.

object

**active_plugins**

Names of currently active WordPress plugins.

Array\<string>

<= 500 items

**php_version**

string

<= 64 characters

### Example

```
8.2.0
```

**wp_version**

string

<= 64 characters

### Example

```
6.5.0
```

**active_theme**

string

<= 256 characters

### Example

```
twentytwentyfour
```

**locale**

string

<= 64 characters

### Example

```
en_US
```

**server_software**

string

<= 256 characters

### Example

```
Apache/2.4.57
```

**db_version**

string

<= 64 characters

### Example

```
8.0.33
```

**memory_limit**

string

<= 32 characters

### Example

```
256M
```

**timezone**

string

<= 64 characters

### Example

```
America/Chicago
```

**active_plugin_count**

Total number of active plugins on the site. May exceed len(active_plugins) when the plugin list was truncated at 500 items.

integer

### Example

```json
12
```

**ssl_valid**

boolean

### Example

```json
true
```

**is_multisite**

boolean

### Example

```json
false
```

**wp_debug**

boolean

### Example

```json
false
```

**wpe_plugins**

Active WP Engine plugins mapped to their version. Optional: on the heartbeat an absent field keeps the stored map, and an empty object means none are active. An empty array is also accepted; any other array is rejected. The gateway keeps only slugs `power`, `power-*` and `wpe-*` (the Power plugin reports as `wpe-hub`), at most 20 in slug order, and drops slugs over 64 characters and entries whose version is empty, not a string or over 32 characters. `POST /v1/sites` accepts the field but does not store it; heartbeats record it.

object

**_key_**

additional properties

string

### Example

```json
{
  "wpe-hub": "1.2.0"
}
```

## Responses

### 200

Site registered or updated. Returns the stable site_id, client_id, registration status, the original creation timestamp (unchanged on updates), and the per-site HMAC secret for anonymous user identification.

Media typeapplication/json

object

**site_id**

required

Stable gateway-owned identifier for the logical site.

string

/^site\_\[0-9A-Za-z]{22}$/

**client_id**

required

The current Dynamic Client Registration credential identifier for the site. It can change when the site reconnects.

string

**status**

required

Current registration status of the site.

string

Allowed values: active revoked suspended

**created_at**

required

When the site was first registered (UTC). Unchanged on subsequent updates to the same site.

string format: date-time

**site_hmac_secret**

required

Per-site HMAC secret for deriving anonymous user identifiers. 32 random bytes hex-encoded (64 chars). Preserved across re-registrations. The plugin must store this securely and never send it back to the gateway.

string

/^\[0-9a-f]{64}$/

#### Example

```json
{
  "site_id": "site_01AbCdEfGhIjKlMnOpQrSt",
  "client_id": "client_01abc",
  "status": "active",
  "created_at": "2024-01-15T10:30:00Z",
  "site_hmac_secret": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"
}
```

### 400

Invalid request body or parameters. On agent create and update, error type “profile_unavailable” means the requested profile is not available to the caller.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 401

Missing or invalid bearer token

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

#### Headers

**WWW-Authenticate**

string

##### Example

```
Bearer realm="ai-services"
```

### 403

The token is valid, but the request is forbidden. Error type “permission_error” means the token’s scope/permission is insufficient or the authenticated user is not authorized for the target account; “entitlement_required” means the account may not use WP Engine AI, with error_code giving why when known (not_eligible, terms_required, not_enabled or suspended) and portal_url where to fix it; “account_required” means no billing account could be resolved; and “billing_account_pending” means billing isn’t set up for the account yet, usually because its setup after AI was turned on hasn’t finished. Retry shortly. On /v1/chat/completions when content moderation is enabled, and on /v1/sessions/{session_id}/events, error type “content_policy_violation” instead means the request content was blocked by content moderation policy; that response never identifies which filter or category matched.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 409

The request conflicts with an existing resource

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 413

Request body exceeds the 64 KiB limit

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

### 503

A required dependency is temporarily unavailable. Retry after a short delay with exponential back-off. An entitlement lookup failure uses error type “entitlement_unavailable”. A model without complete rate coverage uses error type “model_unpriced”. A credit balance that can’t be read uses error type “balance_unavailable”.

Media typeapplication/json

object

**error**

required

object

**message**

required

string

**type**

required

string

**error_code**

Stable, machine-readable cause, set by routes that document their codes (the AI readiness and account AI routes, and an entitlement_required refusal, where it is one of not_eligible, terms_required, not_enabled or suspended); absent elsewhere and on errors from shared layers such as authentication. Clients must allow new codes.

string

**portal_url**

Where the customer can resolve the error, set on an entitlement_required refusal that has an error_code when one is configured.

string

**code**

required

integer

**request_id**

required

Correlation ID for this request, matching the X-Request-ID response header — a 32-character trace ID behind the load balancer, otherwise a UUID. Quote it in support reports.

string

#### Example

```json
{
  "error": {
    "type": "invalid_request_error",
    "request_id": "80f1e2c3a4b5c6d7e8f9a0b1c2d3e4f5"
  }
}
```

## Was this helpful?

Thanks for the feedback!

```json
{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://developers.wpengine.com/#website","name":"WP Engine Product Documentation","url":"https://developers.wpengine.com/","description":"Resources for developing, deploying, and scaling on WP Engine.","inLanguage":"en-US","keywords":["API documentation","product documentation","headless WordPress","managed WordPress hosting"],"publisher":{"@id":"https://wpengine.com/#organization"}}]}
```
