Yoast SEO doesn’t have one API. It has several, and each one does a different job. The REST API reads the rendered SEO title and meta description for any post. Since version 27.7 it can also write them, but only on posts. PHP gives you full read and write access to every field. WP-CLI covers one-off fixes and bulk scripts. And there’s a newer Abilities API layer for AI agents that, today, only reads.
This guide shows each route with code you can paste, plus the places where it fails without telling you. The biggest: a REST write to a page returns 200 OK and saves nothing.
Every snippet was tested on September 24, 2026 against Yoast SEO 28.5 (the current release) on WordPress 7.1.
Which Yoast API Should You Use?
| You want to | Use | Works on |
|---|---|---|
| Read a post’s final SEO title, description, and schema from outside WordPress | yoast_head_json in the core REST API |
Posts, pages, custom post types, terms, authors |
| Read the SEO head for any URL | GET /wp-json/yoast/v1/get_head?url= |
Any indexed URL; no login needed |
| Write a title, description, or focus keyphrase from outside WordPress | POST /wp-json/wp/v2/posts/{id} with meta |
Posts only, unless you register the fields for other types |
| Read or write inside PHP | WPSEO_Meta::get_value() / set_value() |
Any post type |
| Get the rendered value in PHP | YoastSEO()->meta->for_post( $id ) |
Any indexed post |
| Fill empty descriptions automatically | The %%excerpt%% variable, or the wpseo_metadesc filter |
Whole site |
| Fix one post or script a batch from the shell | wp post meta update |
Any post type |
Read Yoast Data With the REST API
Yoast adds two fields to every core REST API response for posts, pages, custom post types, terms, and users: yoast_head, the full head HTML as one string, and yoast_head_json, the same data as an object. Headless sites use these to print the right tags without running Yoast on the front end.
curl "https://example.com/wp-json/wp/v2/posts/123?_fields=id,link,yoast_head_json"
The object includes title, description, canonical, robots, the Open Graph fields (og_title, og_description, og_image, and so on), the Twitter fields, and the full schema graph. Fields that don’t apply to a page are left out. A post with no meta description has no description key at all.
These are rendered values: Yoast has already replaced template variables like %%sitename%%. That makes the REST output the easiest way to see what Google sees.
If you don’t want this data public, turn it off at Yoast SEO → Settings → Site features → REST API endpoint. That setting also turns off get_head.
Read Any URL With get_head
When you have a URL and not a post ID, ask Yoast directly:
curl "https://example.com/wp-json/yoast/v1/get_head?url=https://example.com/pricing/"
It returns { "html": "...", "json": { ... }, "status": 200 }. The HTTP status matches the status field, so a URL Yoast doesn’t know returns 404. The endpoint is public; no login needed.
One trap for developers: Yoast only builds its index of pages (the “indexables” table) on production sites. On a site where WP_ENVIRONMENT_TYPE is local or staging, get_head returns 404 for every URL. That’s expected, not a bug.
Write Yoast Meta Descriptions Through the REST API
For years, the answer to “can I update Yoast meta via REST?” was “not without custom code.” That changed in Yoast 27.7 (May 2026): Yoast now registers the SEO title, meta description, and focus keyphrase with the REST API, so you can write them with a normal authenticated request. Authenticate with a WordPress Application Password (Users → Profile):
curl -X POST "https://example.com/wp-json/wp/v2/posts/123" \
--user "you:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{"meta": {
"_yoast_wpseo_metadesc": "Our 30-day refund policy: who qualifies, how to ask, and how long it takes.",
"_yoast_wpseo_title": "Refund Policy %%sep%% %%sitename%%",
"_yoast_wpseo_focuskw": "refund policy"
}}'
The user needs permission to edit that post. Users who can’t edit it don’t see these fields in API responses. Sending an empty string deletes the value, and Yoast falls back to your template.
The page trap. Yoast only registered the fields for the post post type. Send the same request to /wp/v2/pages/45 and you get 200 OK with the page data back, but the meta is silently dropped. Canonical URLs and robots settings aren’t registered for any post type. To write pages (or a custom post type), register the fields yourself in a small plugin or your theme’s functions.php:
add_action( 'init', function () {
foreach ( array( 'title', 'metadesc', 'focuskw' ) as $field ) {
register_post_meta( 'page', '_yoast_wpseo_' . $field, array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return current_user_can( 'edit_post', $post_id );
},
) );
}
} );
The auth_callback is required. Meta keys that start with an underscore are “protected,” and WordPress refuses to write them over REST without one. Swap 'page' for your custom post type’s slug to cover that too.
Read and Write Yoast Meta in PHP
Inside WordPress, use Yoast’s own helpers. They know the field names and defaults:
$description = WPSEO_Meta::get_value( 'metadesc', $post_id );
WPSEO_Meta::set_value( 'metadesc', 'New description.', $post_id );
Useful keys: title, metadesc, focuskw, canonical, meta-robots-noindex, meta-robots-nofollow, bctitle (breadcrumb title), and is_cornerstone. In the database each one is stored as post meta with a _yoast_wpseo_ prefix, so metadesc becomes _yoast_wpseo_metadesc.
Plain update_post_meta() works too. Yoast watches every _yoast_wpseo_ key and refreshes its index at the end of the request, so a direct write doesn’t leave stale data behind.
Stored value vs. what Google sees
The stored value is often empty or a template. A post with no custom SEO title stores nothing and uses the post type template, which defaults to %%title%% %%page%% %%sep%% %%sitename%%. To get the final text, use Yoast’s Surfaces API:
$meta = YoastSEO()->meta->for_post( $post_id );
echo $meta->title; // "Refund Policy - Acme"
echo $meta->description; // the rendered meta description
// Also available:
YoastSEO()->meta->for_current_page()->canonical;
YoastSEO()->meta->for_url( 'https://example.com/pricing/' ); // false if Yoast doesn't know the URL
To render a template string yourself, wpseo_replace_vars( '%%title%% %%sep%% %%sitename%%', $post ) does the replacement.
Automatic Meta Descriptions in Yoast
Out of the box, Yoast’s description template is empty. A post without a hand-written description gets no meta description tag, and Google writes its own snippet from the page. That isn’t always bad, but you have three ways to change it.
1. Use the excerpt variable (no code). Go to Yoast SEO → Settings → Content types → Posts and set the meta description to %%excerpt%%. Yoast uses the post excerpt. With no excerpt, it takes the first 156 characters of the content, cut at a word boundary.
2. Add a fallback filter. For more control, fill empty descriptions at output time without saving anything:
add_filter( 'wpseo_metadesc', function ( $description, $presentation ) {
if ( '' !== $description || 'post' !== $presentation->model->object_type ) {
return $description;
}
$post = get_post( $presentation->model->object_id );
return $post ? wp_trim_words( get_the_excerpt( $post ), 25, '' ) : $description;
}, 10, 2 );
3. Write real descriptions. Both options above reuse text that wasn’t written as a search snippet, so neither sells the click. For that you need a written description per page: by hand, with Yoast Premium’s AI Generate (one post at a time in the editor, or as drafts in the bulk editor), or with an AI agent. We cover the agent route below.
Whatever writes them, aim for Yoast’s range: its length check marks 120 characters or fewer as too short and anything over 156 as too long.
Yoast and WP-CLI
Yoast meta is ordinary post meta, so standard WP-CLI commands work:
# Read and write one post
wp post meta get 123 _yoast_wpseo_metadesc
wp post meta update 123 _yoast_wpseo_metadesc "Our 30-day refund policy, explained."
# List published posts with no meta description
wp post list --post_type=post --post_status=publish \
--meta_key=_yoast_wpseo_metadesc --meta_compare='NOT EXISTS' \
--fields=ID,post_title
Yoast adds its own commands too. wp yoast index builds or rebuilds the index (add --reindex after a migration or a big import), and wp yoast cleanup removes orphaned index rows.
Yoast, the Abilities API, and MCP
WordPress 6.9 added the Abilities API, a standard way for plugins to describe actions an AI agent can call. The official MCP Adapter exposes those abilities to Claude, Cursor, and other MCP clients.
Yoast 28.2 and later register three read-only abilities: yoast-seo/get-seo-scores, yoast-seo/get-readability-scores, and yoast-seo/get-inclusive-language-scores. Each appears only when that analysis is turned on and only on production sites. Yoast 28.5 also contains code for get-post-seo-data and update-post-seo-data abilities, but doesn’t register them yet. Today, an agent connected through the adapter can read Yoast’s scores but can’t change a meta description through Yoast.
Gotchas Worth Knowing
- Pages ignore REST writes unless you register the fields (see above). The response still says 200.
- Raw values contain variables.
%%title%% %%sep%% %%sitename%%is a template, not a title. Use the Surfaces API oryoast_head_jsonfor real text. - Empty is valid. An empty title means “use the template,” not “broken.” An empty description means no tag, unless your template or a filter fills it.
- Local and staging sites don’t build the index, so
get_head404s and the abilities don’t register there. - Caching. Page caches keep serving the old head until they’re cleared. Test with the REST fields, not the cached front end.
Doing It Across a Whole Site Without the Script
Everything above is the plumbing. The real job is usually “find every weak or missing description on 300 posts, write good ones, and don’t break anything.” That’s a script, a model, a review step, and a way to undo.
That’s the job PressBot’s Yoast tools handle. The Pro admin agent:
- audits your Yoast meta worst first: missing fields, descriptions outside the 120–156 range, duplicates, noindex, and custom canonicals;
- drafts new titles and descriptions from each page’s content using your own AI key;
- proposes the changes as a plan with before and after values. Nothing is written until you approve it, and an applied plan can be restored.
The same tools work over MCP from Claude Code or Cursor, so you can run the whole job from your editor instead of writing the loop.
FAQ
Does Yoast SEO have an API?
Yes, several. It adds yoast_head and yoast_head_json to the WordPress REST API, offers a get_head endpoint for any URL, and makes the SEO title, meta description, and focus keyphrase writable over REST on posts since version 27.7. It also has a PHP Surfaces API and WP-CLI commands. All of these are in the free plugin.
Can I update a Yoast meta description with the REST API?
On posts, yes: send {"meta": {"_yoast_wpseo_metadesc": "..."}} to /wp-json/wp/v2/posts/{id} as a user who can edit the post. On pages and custom post types you first need to register the field with register_post_meta(). Otherwise the write is silently ignored.
How do I get the Yoast meta description in PHP?
WPSEO_Meta::get_value( 'metadesc', $post_id ) returns what’s stored. YoastSEO()->meta->for_post( $post_id )->description returns what’s printed on the page, with variables replaced and templates applied.
What’s the right meta description length for Yoast?
Yoast marks 120 characters or fewer as too short and more than 156 as too long. Google measures in pixels and may rewrite your snippet either way, so treat the range as a guide.
Is the Yoast REST API a security risk?
The read fields expose the same data that’s already in your pages’ HTML head. Writes need an authenticated user who can edit the post. If you’d rather not publish the head data as JSON, turn off the REST API endpoint under Yoast SEO → Settings → Site features.