The WordPress REST API is the JSON interface built into WordPress core that lets any app read and write your site’s content over HTTP. Every site serves it at https://yoursite.com/wp-json/, the core routes live under /wp-json/wp/v2/ (posts are at /wp-json/wp/v2/posts), and on sites with Plain permalinks the same routes answer at ?rest_route=/wp/v2/posts. Public content needs no authentication; anything else needs an Application Password or a logged-in cookie plus nonce.
It’s how the block editor loads and saves posts, how the mobile app talks to your site, and how headless frontends and AI agents treat WordPress as a content backend.
This guide covers the endpoints you’ll use, the four authentication methods, reading and writing with curl and JavaScript, custom endpoints with register_rest_route(), and the exact error codes that break first attempts. The requests, responses, and error messages below were tested on a fresh WordPress 7.1.4 install running in WordPress Playground (November 2026).
- The WordPress REST API turns your site into a backend you can read from and write to from any language, served at
/wp-json/. - It powers the mobile app, the block editor, and increasingly AI-assisted apps built in tools like Claude and Cursor.
- Authenticate write requests with Application Passwords, using a dedicated user on the least-privilege role.
- Set
'show_in_rest' => trueto expose custom post types and fields to the API.
What the WordPress REST API Is
WordPress ships with a built-in JSON API that exposes most of its content (posts, pages, users, comments, media, categories, tags, custom post types, settings) over standard HTTP requests. It’s been part of WordPress core since version 4.7 (December 2016), and modern WordPress relies on it heavily, the Block Editor itself is essentially a REST API client.
The base URL for any WordPress site is:
https://yoursite.com/wp-json/
Hit that URL in a browser and you’ll see a JSON document describing every available endpoint, including the routes registered by your active plugins. The default endpoints live under /wp/v2/:
| Endpoint | What it returns | GET without auth? |
|---|---|---|
/wp/v2/posts | Posts (10 per page by default); /wp/v2/posts/123 for one post | Published posts only |
/wp/v2/pages | Pages | Published pages only |
/wp/v2/media | Attachments; POST here to upload | Yes |
/wp/v2/categories, /wp/v2/tags | Taxonomy terms | Yes |
/wp/v2/comments | Comments | Approved comments only |
/wp/v2/users | Users | Only users with published posts |
/wp/v2/users/me | The authenticated user | No (401 rest_not_logged_in) |
/wp/v2/types, /wp/v2/taxonomies | Registered post types and taxonomies | Yes |
/wp/v2/search | Search across post types | Yes |
/wp/v2/settings | Site settings | No (needs manage_options) |
/wp-abilities/v1/abilities | Abilities API (WordPress 6.9+), used by MCP adapters | No (needs a logged-in user) |
Read operations (GET) on public content work without authentication. Write operations (POST, PUT, DELETE) and non-public data (drafts, private posts, settings, context=edit) require authentication. We’ll cover both.
Why You’d Actually Use It in 2026
The REST API has been around for a decade, but the use cases have shifted. The 2026 reality:
- Vibe-coded apps: prompting Cursor or Claude to build “a Next.js app that pulls from my WordPress site” works because the REST API is documented well enough that LLMs can reason about it. Apps built this way treat WordPress as a content backend without the developer ever opening the WordPress admin.
- Headless and hybrid builds: pulling WordPress content into a Next.js, SvelteKit, Astro, or Hono frontend so the public site is fast and CDN-friendly while editors still get the WordPress admin they know.
- Mobile and desktop apps: the official WordPress mobile app speaks REST under the hood. Custom apps for clients usually do the same.
- AI agents and MCP servers: agents that read or post content through the REST API are increasingly common. WordPress 6.9 added the Abilities API (served at
/wp-json/wp-abilities/v1/), which the official MCP Adapter builds on. Our WordPress MCP guide covers that setup. - Background jobs and integrations: cron scripts, GitHub Actions deploys, Zapier-style automations all hit the REST API to read or post content.
- The WordPress admin itself: every Gutenberg save, every quick-edit, every Site Editor change goes through the REST API.
If you’ve been writing PHP inside WordPress for years and never touched the REST API, the modern dev shift is: less editing PHP, more calling endpoints from outside.
Authentication: Pick the Right Method
This is the part that confuses most people on first contact. WordPress supports four authentication methods and they each have a clean use case.

Application Passwords (default for almost everything)
Built into WordPress core since 5.6 (December 2020). Each user can generate per-app passwords from Users -> Profile -> Application Passwords. Use them with HTTP Basic Auth:
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
https://yoursite.com/wp-json/wp/v2/posts?status=draft&context=edit
This is the right choice for: server-to-server scripts, CLI tools, deploy pipelines, mobile apps, vibe-coded apps, and anything where you control both ends. Our guide to Application Passwords covers generation, revocation, and the HTTP_AUTHORIZATION gotcha.
One rule trips up local testing: Application Passwords only work over HTTPS, or when WP_ENVIRONMENT_TYPE is 'local' (that’s the check in core’s wp_is_application_passwords_supported()). On a plain-HTTP staging site the Authorization header is ignored, and every request runs as a logged-out visitor.
Cookie + Nonce (built-in, same-origin only)
If your code runs in the same browser session as a logged-in WordPress user (admin-side JavaScript, custom Gutenberg blocks, theme JS that talks to WordPress), use cookie auth with a nonce. The traditional way is wpApiSettings.nonce when your scripts are enqueued with wp-api as a dependency. Modern Gutenberg-side code uses the @wordpress/api-fetch package, which handles the nonce automatically. Both end up sending the same X-WP-Nonce header:
fetch( '/wp-json/wp/v2/posts/123', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpApiSettings.nonce,
},
body: JSON.stringify( { title: 'Updated from JS' } ),
} );
The nonce is not optional. Send the cookie without it and WordPress drops the login, so the request runs as a logged-out visitor and is_user_logged_in() returns false inside your endpoint. Cookie auth also doesn’t work cross-origin (a Next.js app on a different domain can’t use this). For that, use Application Passwords.
JWT (plugin)
JSON Web Token authentication isn’t built into WordPress core, but several plugins add it. The most-installed option is JWT Authentication for WP REST API (60,000+ active installs, updated November 2026, tested up to WordPress 7.1.4). miniOrange’s JWT Authentication for WP REST APIs is also current. Avoid the separate jwt-auth plugin (“JWT Auth”), which WordPress.org closed on October 23, 2026 pending review. With the first plugin, the client POSTs a username and password once to /wp-json/jwt-auth/v1/token, gets back a token, and sends it on later requests as a Bearer header.
JWT makes sense for SPAs that want short-lived tokens (every token has an expiry) and don’t want to store user passwords. For most server-to-server use cases in 2026, Application Passwords are simpler.
OAuth 2.0 (plugin)
OAuth is the right choice when an end user authorizes a third-party app to access their data on someone else’s WordPress site. The classic delegated-auth flow. There are several OAuth2 plugins for WordPress (the original WP-API team’s OAuth2 server is the historical reference but isn’t actively maintained; WP OAuth Server is maintained, though as of November 2026 it lists “tested up to” 6.9, so test it on a staging copy first), all handling the authorization flow, refresh tokens, and scope-based permissions.
Overkill for any single-developer use case. Right answer if you’re building something like a third-party WordPress integration on a marketplace.
Reading Data: GET Requests
The simplest case. Hit an endpoint, get JSON back. No auth needed for public content:
# Get the 10 most recent published posts
curl https://yoursite.com/wp-json/wp/v2/posts
# Get one specific post by ID
curl https://yoursite.com/wp-json/wp/v2/posts/123
# Get posts in a specific category
curl https://yoursite.com/wp-json/wp/v2/posts?categories=5
# Search posts
curl https://yoursite.com/wp-json/wp/v2/posts?search=woocommerce
# Pagination
curl https://yoursite.com/wp-json/wp/v2/posts?per_page=50&page=2
The same endpoints from JavaScript:
// Get the 20 most recent posts with their featured images embedded
const res = await fetch(
'https://yoursite.com/wp-json/wp/v2/posts?per_page=20&_embed'
);
const posts = await res.json();
posts.forEach( post => {
console.log( post.title.rendered );
const featured = post._embedded?.[ 'wp:featuredmedia' ]?.[0];
if ( featured ) {
console.log( featured.source_url );
}
} );
The ?_embed parameter tells WordPress to include related resources (author, featured media, terms) inline so you don’t need a follow-up request for each one. It’s the single most useful query parameter for headless builds.
post.title.rendered is filtered HTML, with quotes and dashes turned into entities like ’, so decode it before you use it as plain text. The unfiltered title.raw only comes back with ?context=edit for a user who can edit the post. Our guide to getting a post title from the REST API shows both side by side.
Pagination headers: the response includes X-WP-Total and X-WP-TotalPages headers so you can build “page N of M” UI without a separate count query:
const res = await fetch( '/wp-json/wp/v2/posts?per_page=20' );
const total = res.headers.get( 'X-WP-Total' );
const totalPages = res.headers.get( 'X-WP-TotalPages' );
WordPress also sends a Link header with rel="next" and rel="prev" URLs, and it exposes all three headers to cross-origin JavaScript (Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages, Link). The pagination handbook page has the details.
Query parameters worth knowing
| Parameter | What it does | Notes from testing |
|---|---|---|
per_page | Items per page | Default 10, max 100. per_page=101 returns 400 rest_invalid_param: “per_page must be between 1 (inclusive) and 100 (inclusive)” |
page | Which page of results | Loop until it equals X-WP-TotalPages |
_fields | Return only the listed fields | _fields=id,title,link shrinks the payload a lot; nested keys work too (content.raw) |
_embed | Inline linked resources (author, featured media, terms) | If you also use _fields, include _links and _embedded or the embeds get stripped |
context | view (default), embed, or edit | edit needs auth and returns raw fields like content.raw |
status | Filter by post status | status=draft without auth returns 400 with “Status is forbidden.” |
search, slug | Keyword search; exact slug match | ?slug=hello-world is how headless sites look up a post by URL |
categories, tags | Filter by term IDs | categories=5,7 matches any; categories[terms]=5,7&categories[operator]=AND requires all |
orderby, order | Sorting | e.g. orderby=modified&order=desc |
Writing Data: POST, PUT, DELETE
Authenticated requests can create, update, and delete content. Examples using Application Passwords:
Create a post
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"title": "Hello from the REST API",
"content": "Posted programmatically.",
"status": "publish"
}' \
https://yoursite.com/wp-json/wp/v2/posts
Update a post
curl -u "username:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-X POST \
-d '{ "content": "Updated content here." }' \
https://yoursite.com/wp-json/wp/v2/posts/123
Note: WordPress accepts both POST and PUT for updates. POST to /posts (no ID) creates a new post. POST or PUT to /posts/123 updates that specific post. Pick whichever method matches your client conventions; the WordPress endpoints handle both.
A successful create returns 201 Created with the full post object, including the new id. Send at least a title, content, or excerpt; with all three empty, WordPress refuses with empty_content (“Content, title, and excerpt are empty.”). If you leave out status, WordPress saves a draft. To set categories or a featured image in the same request, pass "categories": [5] and "featured_media": 42 as IDs, not names.
Delete a post
# Move to trash
curl -u "username:app_password" -X DELETE \
https://yoursite.com/wp-json/wp/v2/posts/123
# Force-delete (skip the trash)
curl -u "username:app_password" -X DELETE \
https://yoursite.com/wp-json/wp/v2/posts/123?force=true
Upload a media file
Upload binary data with Content-Type: multipart/form-data or pass the file’s bytes directly with the right Content-Disposition:
curl -u "username:app_password" \
-H "Content-Disposition: attachment; filename=image.jpg" \
-H "Content-Type: image/jpeg" \
--data-binary @/path/to/image.jpg \
https://yoursite.com/wp-json/wp/v2/media
The response includes the new media ID, which you can pass as featured_media when creating or updating a post.
Common WordPress REST API Errors (and What Each Code Means)
Every REST error comes back as JSON with a code, a message, and data.status. These are the ones that show up most, with the exact messages WordPress 7.1.4 returned in testing:
| Status | Code | Message | Usual cause |
|---|---|---|---|
| 404 | rest_no_route | No route was found matching the URL and request method. | Typo in the route, wrong HTTP method, the plugin that registers it is inactive, or a custom post type without show_in_rest |
| 401 | rest_not_logged_in | You are not currently logged in. | No valid credentials reached WordPress: the Authorization header was stripped, or the site is on HTTP instead of HTTPS |
| 401 | rest_forbidden | Sorry, you are not allowed to do that. | A custom route’s permission_callback returned false for a logged-out request |
| 403 | rest_forbidden | Sorry, you are not allowed to do that. | Same check failed, but WordPress knows who you are: the user lacks the capability |
| 401 / 403 | rest_cannot_create | Sorry, you are not allowed to create posts as this user. | 401 means you’re anonymous; 403 means a real user without edit_posts (a Subscriber, for example) |
| 403 | rest_cookie_invalid_nonce | Cookie check failed | Missing, wrong, or expired X-WP-Nonce on a cookie-authenticated request |
| 400 | rest_invalid_param | Invalid parameter(s): per_page | A query or body value failed validation; data.params says which one |
401 Unauthorized (HTTP_AUTHORIZATION header stripped)
The most common first-attempt failure. Many shared hosts strip the HTTP Authorization header from incoming requests before PHP sees it, which means WordPress receives the request with no auth and rejects it as 401, even though your credentials are correct.
The fix is a one-line addition to your .htaccess:
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Goes inside the # BEGIN WordPress block, right after RewriteEngine On. Current WordPress writes this exact rule into that block itself (it’s in WP_Rewrite::mod_rewrite_rules() in 7.1.4), so re-saving Settings -> Permalinks usually fixes an older .htaccess. If it still fails, the REST API handbook’s alternative for Apache is SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1. Nginx ignores .htaccess entirely; the handbook’s fix there is adding fastcgi_pass_header Authorization; to the FastCGI section of the server config.
CORS errors (cross-origin)
If you’re calling the REST API from a frontend on a different domain (Next.js on Vercel hitting WordPress on yoursite.com, for example), the browser blocks the request unless WordPress sends the right CORS headers.
The simplest fix is hooking into rest_pre_serve_request from your functions.php or a custom plugin:
add_action( 'rest_api_init', function() {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
$allowed = [
'https://yourapp.vercel.app',
'https://staging.yourapp.com',
];
$origin = get_http_origin();
if ( $origin && in_array( $origin, $allowed, true ) ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
header( 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS' );
header( 'Access-Control-Allow-Credentials: true' );
header( 'Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce' );
}
return $value;
} );
}, 15 );
Don’t use Access-Control-Allow-Origin: * with Allow-Credentials: true, browsers reject that combination. Always allowlist specific origins.
403 Forbidden (rest_cannot_create, rest_forbidden)
Different from 401. This means WordPress recognized you, but the user doesn’t have the capability to perform the action. Creating posts needs edit_posts (Contributor and up), publishing needs publish_posts (Author and up), and editing other people’s posts needs Editor. Our WordPress user roles guide lists what each role can do. The error response tells you which action failed:
{
"code": "rest_cannot_create",
"message": "Sorry, you are not allowed to create posts as this user.",
"data": { "status": 403 }
}
Either grant the user the right role, or generate the Application Password under a different user that has the right role for what your script needs to do.
Custom Endpoints with register_rest_route()
The default endpoints cover most needs, but sometimes you want a single endpoint that returns exactly the data shape your frontend wants. Register a custom route from your functions.php or a custom plugin:
add_action( 'rest_api_init', function() {
register_rest_route( 'myapp/v1', '/featured-posts', array(
'methods' => 'GET',
'permission_callback' => '__return_true', // public
'callback' => 'myapp_featured_posts',
) );
} );
function myapp_featured_posts( $request ) {
$posts = get_posts( array(
'posts_per_page' => 5,
'meta_key' => 'is_featured',
'meta_value' => '1',
) );
return array_map( function( $post ) {
return array(
'id' => $post->ID,
'title' => $post->post_title,
'permalink' => get_permalink( $post ),
'excerpt' => get_the_excerpt( $post ),
'image' => get_the_post_thumbnail_url( $post, 'large' ),
);
}, $posts );
}
That registers /wp-json/myapp/v1/featured-posts. The pattern: namespace (myapp/v1), route (/featured-posts), method, permission callback, and the actual handler.
Since WordPress 5.5, permission_callback is required. Leave it out and WordPress raises a _doing_it_wrong() notice (visible with WP_DEBUG on): “The REST API route definition for myapp/v1/featured-posts is missing the required permission_callback argument. For REST API routes that are intended to be public, use __return_true as the permission callback.” In testing the route still responded, so the notice is a warning, but it’s there because a missing check is easy to ship by accident. '__return_true' is the documented way to say “this route is public on purpose.” Only use it for data you’d happily show logged-out visitors.
Returning a plain array works because WordPress wraps it for you. Wrap it yourself with rest_ensure_response( $data ) when you want a WP_REST_Response to add headers or a status code, and return a WP_Error with a 'status' in its data to send an error. The adding custom endpoints handbook page covers argument validation with args.
For an authenticated endpoint, swap the permission callback for a capability check. When it returns false, WordPress answers with rest_forbidden: status 401 for logged-out requests, 403 for logged-in users who lack the capability:
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
Custom endpoints are the right pattern for headless frontends that need a tightly-shaped response (smaller payload, fewer round-trips), and for backend integrations that don’t fit the default CRUD model.
A Real Vibe-Coded Example: Next.js + WordPress
If you ask Claude or Cursor to “build a Next.js blog that pulls posts from my WordPress site”, the resulting code looks roughly like this:
// app/page.tsx in Next.js App Router
export const revalidate = 3600; // ISR: refresh once per hour
export default async function HomePage() {
const res = await fetch(
'https://yoursite.com/wp-json/wp/v2/posts?per_page=10&_embed',
{ next: { revalidate: 3600 } }
);
const posts = await res.json();
return (
<main>
<h1>Latest Posts</h1>
{posts.map( ( post: any ) => (
<article key={post.id}>
<h2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} />
<div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
</article>
) )}
</main>
);
}
That’s the entire integration. Your editors keep using the WordPress admin they know; your public site is a Vercel deploy that pulls fresh data once an hour. Application Passwords aren’t even needed here because public posts don’t require auth.
For previews of draft posts (or any non-public content), pass the credentials:
const username = process.env.WP_USERNAME!;
const password = process.env.WP_APP_PASSWORD!;
const auth = Buffer.from( `${username}:${password}` ).toString( 'base64' );
const res = await fetch(
'https://yoursite.com/wp-json/wp/v2/posts?status=draft&context=edit',
{ headers: { Authorization: `Basic ${auth}` } }
);
Store the WP_APP_PASSWORD as an environment variable, never in source. Generate it from Users -> Profile -> Application Passwords in WordPress.
Debugging Failed Requests
When a request isn’t behaving, the standard checks:
- Hit the endpoint in a browser first (without auth) to confirm the URL is right and WordPress is reachable.
https://yoursite.com/wp-json/should return JSON listing every namespace. - Test with curl from the command line before debugging your app code. Curl strips most layers (no CORS, no app middleware), which isolates whether WordPress itself is rejecting the request.
- Check
context=edit: most fields are only returned when this query param is present. If your draft post is missing the rawcontent, that’s why. - Read the actual error body: WordPress returns structured JSON errors with
code,message, anddata.status. The code (e.g.,rest_invalid_param,rest_no_route) tells you exactly what’s wrong. - Enable WordPress debug mode to surface PHP errors thrown inside your custom endpoints.
WP_DEBUG_LOGwrites them to/wp-content/debug.loginstead of dumping them in the response.
Should You Disable the REST API?
Some older “WordPress hardening” guides recommend disabling the REST API for security. Don’t do this. The REST API handbook’s own FAQ says you should not disable it, because WordPress core uses it for the block editor, the Site Editor, and plugin and theme screens. Disabling it breaks the admin in non-obvious ways. The old Disable REST API plugin hasn’t had a release since September 2023 either.
If you want a private API, require a login instead. This is the handbook’s pattern using the rest_authentication_errors filter. In testing, logged-out requests got a 401 rest_not_logged_in while Application Password requests still worked:
add_filter( 'rest_authentication_errors', function( $result ) {
// Respect an earlier auth result (true = authenticated, WP_Error = failed).
if ( true === $result || is_wp_error( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_not_logged_in',
__( 'You are not currently logged in.' ),
array( 'status' => 401 )
);
}
return $result;
} );
That also blocks anonymous readers, so skip it if a headless frontend or anything else reads your public content without credentials.
The narrower fix most sites want: hide the /users endpoint from logged-out visitors (by default it lists every user who has published a post, including their login slug). One filter does it:
add_filter( 'rest_endpoints', function( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'] );
unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
} );
In testing, logged-out requests to /wp/v2/users then got a 404 rest_no_route, while authenticated requests (including the block editor’s) still saw the full list.
Frequently Asked Questions
Is the WordPress REST API enabled by default?
Yes, since WordPress 4.7 (December 2016). Every modern WordPress site exposes the REST API with no setup required. If you’re hitting /wp-json/ directly and getting a 404, your permalink structure is set to “Plain”, which is the only permalink mode where the pretty URL doesn’t work. The REST API still functions; you just have to use the alternate query-string form: https://yoursite.com/?rest_route=/wp/v2/posts. Switching to “Post name” (or any non-plain permalink) makes the /wp-json/ URL work too.
What’s the difference between /wp-json/wp/v2/ and other namespaces?
/wp/v2/ is the core WordPress namespace (posts, pages, users, etc.). Plugins and custom code register their own namespaces (/woocommerce/v3/, /jetpack/v4/, /myapp/v1/). Hit /wp-json/ in a browser to see every namespace your site exposes.
Can I use the REST API without authentication?
For read-only access to public content (published posts, pages, public taxonomies, public custom post types), yes. For drafts, private content, users, settings, and any write operation, you need authentication. Application Passwords is the simplest auth method for almost every use case.
How do I get the raw post content (not the rendered version)?
Add ?context=edit to your request and authenticate as a user with edit_posts capability. By default the REST API returns rendered content (HTML with shortcodes processed). With context=edit, you get the raw block markup as it lives in the database, which is what you want for round-trips that update content.
Does the REST API work with custom post types?
Yes, but only if the custom post type was registered with 'show_in_rest' => true. Older custom post types (or ones from older plugins) often have this set to false by default, which is why they don’t appear in the REST API. The fix is editing the register_post_type() call to enable REST support, or filtering it via register_post_type_args.
How do I rate-limit the REST API?
WordPress core doesn’t include rate limiting. For public-facing endpoints, do it at the host or edge level (Cloudflare rate-limiting rules, Nginx limit_req, or your managed host’s WAF), where blocked requests never reach PHP.
Why isn’t my WordPress REST API working?
Start with https://yoursite.com/wp-json/ in a browser. A 404 HTML page usually means a rewrite problem: re-save Settings -> Permalinks, or try ?rest_route=/. JSON that says rest_no_route means the route or method is wrong. A 401 on a request you sent credentials with means they never arrived (HTTP instead of HTTPS, or a stripped Authorization header), and a 403 means the user lacks the capability. A security plugin or host firewall blocking /wp-json/ is the other common cause; our WordPress 403 error guide covers firewall rules that misfire on REST requests.
How do I get a WordPress REST API key?
Self-hosted WordPress doesn’t issue API keys. The built-in equivalent is an Application Password: go to Users -> Profile, scroll to Application Passwords, name it, and click Add. Use your username plus that 24-character password with HTTP Basic Auth. Each one can be revoked on its own without changing your login password.
What is the WordPress REST API base URL?
https://yoursite.com/wp-json/ is the index, and core routes start at https://yoursite.com/wp-json/wp/v2/. If WordPress lives in a subdirectory, the base follows it (https://example.com/blog/wp-json/). With Plain permalinks, use https://yoursite.com/?rest_route=/wp/v2/posts. In PHP, rest_url() or get_rest_url() returns the correct one for the current site.
Is the WordPress.com REST API the same as the WordPress REST API?
Not quite. Self-hosted sites serve the core API from their own domain at /wp-json/. WordPress.com sites are reached through public-api.wordpress.com, which offers the core /wp/v2/ routes (for example public-api.wordpress.com/wp/v2/sites/{site}/posts) plus its older /rest/v1.1/ API, and uses OAuth2 for authentication. The WordPress.com API docs cover that side.
Is permission_callback __return_true safe for a public endpoint?
Yes, when the route only returns data you’d show logged-out visitors anyway. '__return_true' is exactly what core’s own warning message tells you to use for public routes. It’s unsafe on any route that writes data or returns private fields; use a capability check such as current_user_can( 'edit_posts' ) there.
What fields are required to create a user with the REST API?
username, email, and password are required on POST /wp/v2/users, and the request must come from a user with create_users (an Administrator). If you don’t pass roles, WordPress assigns the New User Default Role from Settings -> General.
Can AI agents use the WordPress REST API?
Yes, and this is increasingly common in 2026 (see our WordPress MCP guide for the agent-driven version). The pattern is: generate an Application Password under a dedicated agent user, give that user the minimum role required for the agent’s job (Editor for content creation, Author if it should only post under its own name, custom roles for narrower scopes), then have the agent authenticate with Basic Auth on every request.
Wrapping Up
The WordPress REST API is the most flexible part of modern WordPress. The core mental model: every piece of content WordPress knows about is reachable via a JSON endpoint, you authenticate with Application Passwords for almost everything, and you can extend it with custom routes when the defaults aren’t enough.
If you’re starting fresh in 2026 and want to use WordPress as a backend for a Next.js / SvelteKit / Astro / Hono frontend (or as a content database for an AI-assisted app), the REST API is the right surface. The cluster of nearby pillars is worth bookmarking together: Application Passwords for auth, .htaccess for the HTTP_AUTHORIZATION fix, debug mode for surfacing errors, functions.php for registering custom routes, and hooks for plumbing the lot together.
Have a vibe-coded WordPress app you’ve shipped or want to ship? Drop a note in the comments.


