> ## Documentation Index
> Fetch the complete documentation index at: https://developers.semji.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect WordPress to Semji

> Step-by-step guide to connect your WordPress site to Semji with an application password, so AI agents can list, read, create, and update posts — including SEO titles and meta descriptions with Yoast SEO, Rank Math, or SEOPress — and read your categories and tags.

Semji connects to WordPress through the WordPress REST API (`/wp-json/`) using an **application password**. Once connected, Semji's AI agents can list, read, create, and update posts on your site, update your SEO titles and meta descriptions — whatever your SEO plugin (Yoast SEO, Rank Math, or SEOPress) — and read your categories and tags to file and label articles correctly. Each action can be toggled individually.

Compatible from **WordPress 5.6**.

## Prerequisites

* **WordPress 5.6 or newer** — application passwords became a core feature in 5.6.
* A site served over **HTTPS** and reachable from the **public internet**. Private or reserved addresses (localhost, `10.x`, `192.168.x`, …) are rejected.
* The **REST API enabled** at `https://your-site.com/wp-json/`. It is on by default, but some security plugins disable it.
* A WordPress user allowed to manage posts (**Author** role or higher — use **Editor** if agents should be able to update any post).
* If a firewall, WAF, or anti-bot protection (Cloudflare, Akamai, Sucuri, …) sits in front of your site, it must **allow Semji's IP addresses** — see [Firewalls, Cloudflare, and IP allowlisting](#firewalls-cloudflare-and-ip-allowlisting).
* You are a **workspace owner** in Semji. The connection is shared with the whole workspace.

<Note>
  Availability of the WordPress integration depends on your Semji plan. If the
  **Connect** button opens an upgrade dialog instead of the connection form,
  contact your account manager.
</Note>

## Step 1 — Create an application password in WordPress

<Steps>
  <Step title="Open the user profile">
    In wp-admin, go to **Users → Profile** (or **Users → All Users** and open
    the account Semji will use).
  </Step>

  <Step title="Create the application password">
    Scroll down to the **Application Passwords** section, enter a name such as
    `Semji`, and click **Add New Application Password**.
  </Step>

  <Step title="Copy the generated password">
    WordPress displays the password **only once**, formatted like
    `xxxx xxxx xxxx xxxx xxxx xxxx`. Copy it now — you can paste it into Semji
    with or without the spaces.
  </Step>
</Steps>

<Note>
  Don't see the **Application Passwords** section? Your site is either not
  served over HTTPS, running a WordPress version older than 5.6, or a security
  plugin has disabled the feature.
</Note>

## Step 2 — Connect from Semji

<Steps>
  <Step title="Open the CMS integrations">
    In Semji, go to **Settings → Integrations**, open the **CMS** tab, and
    click **Connect** on the WordPress row.
  </Step>

  <Step title="Enter your credentials">
    Fill in the three fields:

    * **Site URL** — the canonical HTTPS URL of your site, e.g.
      `https://your-site.com`. Semji does not follow redirects, so use the
      exact URL your site resolves to (`www` vs non-`www` matters — see
      [troubleshooting](#troubleshooting)).
    * **Email or username** — the WordPress login or email address of the
      user who owns the application password. WordPress accepts either: it
      matches the username first, then falls back to the email address.
    * **Application password** — the password generated in step 1.

    Click **Next**. Semji tests the connection live by calling
    `GET /wp-json/wp/v2/users/me` on your site with these credentials, and
    detects which SEO plugin your site runs.
  </Step>

  <Step title="Pick the WordPress tools">
    Choose what AI agents can do on your site. All eight tools are enabled by
    default: **Create posts**, **Read posts**, **List posts**, **Update
    posts**, **Update meta descriptions**, **Update SEO titles**, **List
    categories**, and **List tags**. You can change this selection at any time
    later.

    **Update meta descriptions** and **Update SEO titles** may need a
    prerequisite configuration on your WordPress site to work, depending on
    your SEO plugin — see [Enable SEO title and meta description
    updates](#enable-seo-title-and-meta-description-updates).
  </Step>

  <Step title="Activate">
    Click **Activate**. The WordPress row now shows as connected.
  </Step>
</Steps>

## What Semji can do once connected

| Tool                     | What it does                                                                                                                                                              |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List posts               | Search and list posts with status filters and pagination                                                                                                                  |
| Read posts               | Fetch a post's full content, including the raw editor source and its current SEO title and meta description                                                               |
| Create posts             | Create new posts — always as **draft** unless you explicitly ask an agent to publish                                                                                      |
| Update posts             | Update only the fields you change (title, content, status, categories, tags)                                                                                              |
| Update meta descriptions | Update the SEO meta description of a post through your SEO plugin — may require a prerequisite configuration, see [below](#enable-seo-title-and-meta-description-updates) |
| Update SEO titles        | Update the SEO title (the `<title>` tag, not the post headline) of a post through your SEO plugin — same prerequisite as meta descriptions                                |
| List categories          | Search and list categories, with their hierarchy (parent category), so agents resolve category names before filing an article                                             |
| List tags                | Search and list tags, so agents resolve tag names before labeling an article                                                                                              |

## Supported SEO plugins

Semji detects your SEO plugin automatically — no configuration to pick a
provider. Detection reads the namespaces advertised by your REST API index
(`/wp-json/`), and falls back to inspecting the SEO fields exposed on your
posts when a security plugin filters the index. The SEO title and meta
description are then read and written through the detected plugin's own
fields:

| SEO plugin | SEO title field          | Meta description field  |
| ---------- | ------------------------ | ----------------------- |
| Yoast SEO  | `_yoast_wpseo_title`     | `_yoast_wpseo_metadesc` |
| Rank Math  | `rank_math_title`        | `rank_math_description` |
| SEOPress   | `_seopress_titles_title` | `_seopress_titles_desc` |

<Note>
  **All in One SEO is not supported.** It stores its metadata in a custom
  database table instead of standard post fields, so it cannot be updated
  through the WordPress REST API. The SEO update tools report an explicit
  error on AIOSEO sites; everything else in the integration works normally.
</Note>

<Warning>
  If several SEO plugins are active at the same time (typically during a
  migration), Semji picks the first supported one in this order: Yoast SEO,
  then Rank Math, then SEOPress. Deactivate the plugin you no longer use so
  updates target the one that actually renders your pages.
</Warning>

## Enable SEO title and meta description updates

Whether a prerequisite is needed depends on your SEO plugin and its version:

* **Yoast SEO 28.1 and newer** — nothing to do. Recent Yoast versions expose
  their SEO title and meta description fields to the REST API out of the box,
  and Semji can update them right away.
* **Older Yoast SEO, Rank Math, and SEOPress** — these store the SEO title and
  meta description in protected custom fields that the REST API does not
  accept writes on by default. A small PHP snippet must register the fields
  with the REST API first.

There are two equivalent ways to install the snippet — entirely from the
WordPress admin with a snippet plugin (no server access needed), or as a
**must-use plugin** file dropped on the server. Pick one. The same snippet
covers all three supported SEO plugins and every public post type (posts,
pages, and custom post types alike), so it keeps working if you later switch
SEO plugins or add content types.

Writes remain protected by WordPress permissions: only users allowed to edit
posts (the `edit_posts` capability) can modify the fields — the same
permission level as editing the post itself.

<Tabs>
  <Tab title="From the WordPress admin">
    Use a snippet plugin such as [Code
    Snippets](https://wordpress.org/plugins/code-snippets/) — it stores the
    code in the database and runs it on every load, so nothing touches the
    server's filesystem.

    <Steps>
      <Step title="Install the plugin">
        In wp-admin, go to **Plugins → Add New Plugin**, search for **Code
        Snippets**, then install and activate it.
      </Step>

      <Step title="Create the snippet">
        In the new **Snippets** menu, click **Add New**, give the snippet a
        name such as `Semji — SEO fields REST support`, and paste the code
        below. It starts without the opening `<?php` line — Code Snippets adds
        it itself.

        ```php theme={null}
        /**
         * Plugin Name: Semji — SEO fields REST support
         * Description: Registers the SEO title and meta description fields of
         * the active SEO plugin (Yoast SEO, Rank Math, or SEOPress) with the
         * WordPress REST API so they can be read and updated by authorized
         * applications, on every public post type. Writing requires the
         * edit_posts capability.
         */

        add_action('init', function () {
            // Each pair of fields is registered only while its plugin is active,
            // so an inactive plugin's fields are never exposed by mistake.
            $seo_meta_keys_by_plugin_constant = [
                'WPSEO_VERSION'      => ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'],
                'RANK_MATH_VERSION'  => ['rank_math_title', 'rank_math_description'],
                'SEOPRESS_VERSION'   => ['_seopress_titles_title', '_seopress_titles_desc'],
            ];

            foreach ($seo_meta_keys_by_plugin_constant as $plugin_constant => $seo_meta_keys) {
                if (!defined($plugin_constant)) {
                    continue;
                }

                foreach (get_post_types(['public' => true], 'names') as $post_type) {
                    foreach ($seo_meta_keys as $meta_key) {
                        register_post_meta($post_type, $meta_key, [
                            'type'          => 'string',
                            'single'        => true,
                            'show_in_rest'  => true,
                            'auth_callback' => static function () {
                                return current_user_can('edit_posts');
                            },
                        ]);
                    }
                }
            }
        }, 20);
        ```

        The snippet detects which of the three plugins is active and only
        registers that plugin's fields, so it keeps working if you later
        switch SEO plugins.
      </Step>

      <Step title="Run it everywhere and activate">
        Keep the scope on **Run snippet everywhere** — REST API requests must
        load it too, so do not restrict it to the admin area — then click
        **Save Changes and Activate**.
      </Step>
    </Steps>

    Deactivating the snippet removes the capability.
  </Tab>

  <Tab title="On the server">
    A must-use plugin is a single PHP file dropped into
    `wp-content/mu-plugins/`. Must-use plugins are loaded automatically by
    WordPress — there is nothing to activate, and removing the file removes
    the capability.

    <Steps>
      <Step title="Create the plugin file">
        On your server, create the directory `wp-content/mu-plugins/` if it
        does not exist yet, then create a file named
        `semji-seo-fields.php` inside it.
      </Step>

      <Step title="Paste the plugin code">
        ```php semji-seo-fields.php theme={null}
        <?php
        /**
         * Plugin Name: Semji — SEO fields REST support
         * Description: Registers the SEO title and meta description fields of
         * the active SEO plugin (Yoast SEO, Rank Math, or SEOPress) with the
         * WordPress REST API so they can be read and updated by authorized
         * applications, on every public post type. Writing requires the
         * edit_posts capability.
         */

        add_action('init', function () {
            // Each pair of fields is registered only while its plugin is active,
            // so an inactive plugin's fields are never exposed by mistake.
            $seo_meta_keys_by_plugin_constant = [
                'WPSEO_VERSION'      => ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'],
                'RANK_MATH_VERSION'  => ['rank_math_title', 'rank_math_description'],
                'SEOPRESS_VERSION'   => ['_seopress_titles_title', '_seopress_titles_desc'],
            ];

            foreach ($seo_meta_keys_by_plugin_constant as $plugin_constant => $seo_meta_keys) {
                if (!defined($plugin_constant)) {
                    continue;
                }

                foreach (get_post_types(['public' => true], 'names') as $post_type) {
                    foreach ($seo_meta_keys as $meta_key) {
                        register_post_meta($post_type, $meta_key, [
                            'type'          => 'string',
                            'single'        => true,
                            'show_in_rest'  => true,
                            'auth_callback' => static function () {
                                return current_user_can('edit_posts');
                            },
                        ]);
                    }
                }
            }
        }, 20);
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

### Verify the fields are exposed

Whichever option you chose, call your REST API with the same credentials
Semji uses, on any post ID, checking your plugin's meta description field
(here Rank Math — swap in the field from the [table
above](#supported-seo-plugins) for Yoast SEO or SEOPress):

```bash theme={null}
curl -s -u 'USERNAME:APP_PASSWORD' \
  'https://your-site.com/wp-json/wp/v2/posts/123' | jq '.meta.rank_math_description'
```

A string — even an empty one (`""`) — means the field is exposed and Semji
can update it. `null` means the code is not running yet.

<Note>
  Without this prerequisite configuration, the **Update meta descriptions**
  and **Update SEO titles** tools fail with an explicit error naming your SEO
  plugin and the missing field, and change nothing on your site — agents will
  tell you the field is not exposed rather than pretend the update succeeded.
  Everything else in the integration works normally.
</Note>

## Manage or disconnect

From **Settings → Integrations → CMS**, the WordPress row offers:

* **Manage tools** — enable or disable each of the eight tools.
* **Disconnect** — remove the integration for the whole workspace.

## Firewalls, Cloudflare, and IP allowlisting

Semji's servers call your WordPress REST API directly. If your site sits
behind Cloudflare or another WAF, you must allow those calls or the
connection will fail.

Semji's servers reach the internet through a fixed set of outbound IP addresses. If a firewall, WAF, or anti-bot protection (Cloudflare, Akamai, Sucuri, …) sits in front of your site, it can block Semji's requests — typically with a `403 Forbidden` — before they ever reach your CMS, even when your credentials are correct.

Allow the following Semji IP addresses:

```text theme={null}
63.34.75.122
63.35.78.179
54.228.104.165
18.200.156.37
34.248.117.83
52.213.28.177
```

On **Cloudflare**, create a WAF custom rule with the **Skip** action matching
requests where the source IP is one of the addresses above and the path
starts with `/wp-json/`.

<Warning>
  A WAF block is indistinguishable from bad credentials: when a protection
  layer answers `403 Forbidden` on `/wp-json/`, Semji shows **"Invalid
  WordPress credentials"** even though your login and application password
  are correct. If you are sure of the credentials, check the firewall first.
</Warning>

## Troubleshooting

<AccordionGroup>
  <Accordion title="&#x22;Invalid WordPress credentials. Check the email or username and the application password.&#x22;">
    Your site answered `401` or `403`. Either the login (email or username)
    or application password is wrong or was revoked, application passwords are disabled on
    the site, or — very commonly — a WAF or anti-bot layer (Cloudflare,
    Wordfence, …) is blocking Semji before the request reaches WordPress. See
    [Firewalls, Cloudflare, and IP
    allowlisting](#firewalls-cloudflare-and-ip-allowlisting).
  </Accordion>

  <Accordion title="&#x22;Could not connect to the WordPress site. Check credentials and site URL.&#x22;">
    Semji could not complete the request. Frequent causes:

    * **The URL redirects.** Semji follows no redirects: if
      `https://example.com` redirects to `https://www.example.com`, enter the
      `www` URL. Same for HTTP→HTTPS or trailing-path redirects.
    * **The REST API is not served** — `/wp-json/` returns 404 (REST API
      disabled by a plugin, or permalink/rewrite issues on the server).
    * **The site is unreachable** — DNS failure, host down, or the domain
      resolves to a private network address.
    * **The site is too slow** — responses must arrive within 10 seconds.
  </Accordion>

  <Accordion title="URL validation errors before submitting">
    * *"Please enter a valid URL."* — the value does not parse as a URL.
    * *"The site URL must start with https\://."* — HTTP sites cannot be
      connected.
    * *"The site URL points to a private or reserved address, which is not
      allowed."* — localhost and private-network hosts are rejected; the site
      must be publicly reachable.
  </Accordion>

  <Accordion title="An agent reports a WordPress error after the connection worked">
    * *"WordPress authentication failed. Please check the credentials in the
      integration settings."* — the application password was revoked or
      changed since connection. Reconnect with a fresh one.
    * *"No WordPress integration configured for this workspace."* — the
      integration was disconnected; a workspace owner must reconnect it.
    * *"No supported SEO plugin detected on this WordPress site…"* — Semji
      found neither Yoast SEO, Rank Math, nor SEOPress on the site. Activate
      one of them, or if one is already active, apply the [prerequisite
      configuration](#enable-seo-title-and-meta-description-updates) so its
      fields are visible through the REST API.
    * *"The meta description was NOT saved: this WordPress site runs … but
      does not expose its meta description field…"* (same wording for the SEO
      title) — the site does not accept REST API writes on your SEO plugin's
      field yet. Apply the prerequisite configuration described in [Enable
      SEO title and meta description
      updates](#enable-seo-title-and-meta-description-updates).
  </Accordion>
</AccordionGroup>

## Related

* [Sync drafts to your CMS](/guides/sync-drafts-to-cms) — API-based publishing flow, if you prefer to drive the sync yourself.
