The WordPress REST API: a developer's guide to authentication, custom endpoints and security

By: Irina Shvaya | October 1, 2026
This guide is part of our WordPress resource hub: costs and hosting, the block editor, plugins, SEO and speed, security, maintenance, WooCommerce, development, comparisons and migrations.

The WordPress REST API is the HTTP interface that lets other applications, and WordPress's own JavaScript, read and write site data as JSON. The official handbook describes it as "an interface for applications to interact with your WordPress site by sending and receiving data as JSON." The block editor uses it for nearly everything it does, headless front ends use it to fetch content, and plugins use it to expose their own data and actions.

Content endpoints have been part of core since WordPress 4.7 (December 2016), and Application Passwords for authenticating external clients arrived in 5.6. As of October 2026 (WordPress 7.1.2), the essentials are unchanged: routes live under /wp-json/, logged-in JavaScript authenticates with cookies plus a nonce, external tools use application passwords over HTTPS, and every custom endpoint must declare a permission_callback.

This guide covers how routes work, how to read and write data, the authentication options and when to use each, how to register a secure custom endpoint, and a security checklist for production sites.

Key Takeaways

  • REST routes live under /wp-json/ (or ?rest_route= on sites without pretty permalinks), with core content under the wp/v2 namespace.
  • Code running inside WordPress authenticates with the login cookie plus a wp_rest nonce sent in the X-WP-Nonce header; without the nonce the request is treated as logged out.
  • External scripts and services should use Application Passwords over HTTPS, which by default are only available on HTTPS sites or local environments.
  • Register custom routes with register_rest_route() on rest_api_init, and always supply a permission_callback; use __return_true only for genuinely public data.
  • Validate and sanitize every argument, check capabilities rather than relying on nonces, and return data or WP_Error objects instead of echoing output.

Routes, endpoints and namespaces

A route is a URL pattern such as /wp/v2/posts/123, and an endpoint is a route plus an HTTP method (GET to read, POST to create or update, DELETE to delete). Routes are grouped into namespaces: core uses wp/v2, and plugins should use their own, such as my-plugin/v1, so versions can change without breaking clients.

On a site with pretty permalinks, the root is https://example.com/wp-json/, which lists every registered route. Without pretty permalinks, the handbook says the route is passed as a query parameter instead, for example https://example.com/?rest_route=/wp/v2/posts/123.

Core endpoints cover posts, pages, media, users, comments, taxonomies, settings, block types, templates, global styles, application passwords and more. Custom post types and taxonomies appear automatically when registered with show_in_rest set to true (which the block editor requires anyway). Since WordPress 6.9, the Abilities API adds its own namespace, wp-abilities/v1, covered in our Abilities API guide.

Reading and writing data

Public content can be read without authentication. A few examples from the command line:

  • Latest posts: curl https://example.com/wp-json/wp/v2/posts
  • Only the fields you need: curl "https://example.com/wp-json/wp/v2/posts?_fields=id,title,link". The global _fields parameter makes WordPress skip unneeded fields while building the response, which is faster as well as smaller.
  • Related objects in one request: add _embed to include linked resources such as the featured image and author.
  • Pagination: per_page (up to 100) and page, with totals in the X-WP-Total and X-WP-TotalPages response headers.

Writing requires an authenticated user with the right capability. For example, updating a post title is a POST to /wp/v2/posts/123 with {"title":"Hello Moon"}; the user must be able to edit that post. Responses use standard HTTP status codes (401 when authentication is missing, 403 when the user lacks permission, 404 for unknown routes or items) plus a JSON body with a code and message.

Authentication options

The REST API authentication handbook describes three approaches.

MethodUse it forHow it worksWatch out for
Cookie + nonceJavaScript running inside WordPress (admin screens, blocks, front-end features for logged-in users)The browser sends the login cookie; your code sends a wp_rest nonce in the X-WP-Nonce header or _wpnonce parameterWithout the nonce, WordPress sets the user to 0 and the request is anonymous; cached pages can serve expired nonces
Application PasswordsExternal scripts, mobile apps, integrations, CI jobs, AI agentsHTTP Basic Auth with the username and an application password, over HTTPSEach password has the full permissions of its user; create a dedicated, least-privilege user
Authentication pluginsOAuth or token flows required by a specific clientThe handbook mentions OAuth 1.0a Server and JSON Web Tokens pluginsYou take on that plugin's maintenance and security record

Cookie authentication with a nonce

If you use @wordpress/api-fetch (the wp-api-fetch script), WordPress configures the root URL and a nonce middleware for you when the script is enqueued, so apiFetch( { path: '/wp/v2/posts' } ) just works for the logged-in user. For manual fetch() calls, create the nonce in PHP with wp_create_nonce( 'wp_rest' ), pass it to your script (for example with wp_add_inline_script() or through Interactivity API server state), and send it as the X-WP-Nonce header. The handbook notes you do not need to verify that nonce in your own callback; core does it in rest_cookie_check_errors().

Application Passwords

Application Passwords are generated per user on the user's profile screen (Users, then Edit User) and can be revoked individually. Use them with Basic Auth over HTTPS, for example curl --user "api-bot:xxxx xxxx xxxx xxxx xxxx xxxx" https://example.com/wp-json/wp/v2/users/me?context=edit. In core, the feature is available by default only when the site uses HTTPS or its environment type is set to local; the wp_is_application_passwords_available filter changes that. Do not use the old Basic Authentication plugin on production; the handbook says it "should only be used for development and testing."

Application password hardening is on the published WordPress 7.2 roadmap along with a sudo-mode re-authentication feature, but the roadmap itself warns that listed items may not all ship. Check the 7.2 release notes before relying on any change.

Registering a custom endpoint

Custom endpoints are registered with register_rest_route() inside a callback on the rest_api_init hook, so the work only happens when the API is loaded. The adding custom endpoints handbook page is the reference. A short, secure example that returns a summary of one post the current user is allowed to read:

  1. Register the route: add_action( 'rest_api_init', function () { register_rest_route( 'my-plugin/v1', '/summary/(?P<id>\d+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => 'my_plugin_get_summary', 'permission_callback' => function ( WP_REST_Request $request ) { return current_user_can( 'read_post', (int) $request['id'] ); }, 'args' => array( 'id' => array( 'validate_callback' => function ( $value ) { return is_numeric( $value ); }, 'sanitize_callback' => 'absint' ) ) ) ); } );
  2. Write the callback: function my_plugin_get_summary( WP_REST_Request $request ) { $post = get_post( $request['id'] ); if ( ! $post ) { return new WP_Error( 'my_plugin_not_found', __( 'Post not found.', 'my-plugin' ), array( 'status' => 404 ) ); } return rest_ensure_response( array( 'id' => $post->ID, 'title' => get_the_title( $post ), 'words' => str_word_count( wp_strip_all_tags( $post->post_content ) ) ) ); }
  3. Call it: GET /wp-json/my-plugin/v1/summary/123, with a nonce from the browser or an application password from outside.

Points worth copying from this pattern:

  • permission_callback is mandatory. Since WordPress 5.5, omitting it triggers a _doing_it_wrong notice. Return true, false or a WP_Error. For routes that really are public, the handbook says to use __return_true so the intent is explicit.
  • Check capabilities, not just login. read_post and edit_post map to the correct rules for private posts, drafts and custom post types.
  • Use args for validation and sanitization. validate_callback rejects bad input with an error; sanitize_callback cleans it before your callback runs. The handbook warns against passing PHP functions such as is_numeric directly as validate_callback, because the extra parameters WordPress passes cause warnings, which is why the example wraps it in a closure.
  • Return, do not echo. Return data, a WP_REST_Response or a WP_Error. The handbook notes that since 5.5, calling wp_send_json() during a REST request triggers a _doing_it_wrong notice.

For larger APIs, extend WP_REST_Controller and register routes in its register_routes() method. You get conventional get_items, create_item and matching *_permissions_check methods plus a JSON schema, which also documents your API at the namespace's index route.

To expose custom fields on existing objects instead of creating new routes, register meta with show_in_rest set to true, or use register_rest_field() for computed values. Our posts on using the REST API in a WordPress plugin and building API-first WordPress go further into structure and versioning.

Security checklist for REST endpoints

  1. Every route has a real permission_callback. Audit for __return_true on anything that writes data or returns non-public information.
  2. Nonces are not authorization. The nonces documentation says they "should never be relied on for authentication, authorization, or access control." Always pair them with capability checks.
  3. Validate, sanitize, then use. Treat every parameter as hostile. Use $wpdb->prepare() for any SQL.
  4. Return only what clients need. Do not include email addresses, tokens or internal IDs in public responses, and respect the context parameter (view versus edit).
  5. Least privilege for integrations. Create a dedicated user with the lowest role that works, give it its own application password, and revoke it when the integration ends. See our guide to WordPress security practices for developers.
  6. Do not disable the REST API. The REST API FAQ warns that disabling it breaks WordPress admin functionality. If a site must be private, the FAQ shows how to require authentication for all requests with the rest_authentication_errors filter.
  7. Keep core and plugins current. REST bugs do happen in core: the WordPress 7.0.2 security release in July 2026 fixed a REST API batch-route confusion issue combined with SQL injection that could lead to remote code execution. Only the latest WordPress version is actively supported.
  8. Rate-limit at the edge. WordPress has no built-in REST rate limiting, so login-like or expensive endpoints should be protected by your host, CDN or web application firewall.

The REST API in headless and AI workflows

Headless front ends built with frameworks such as Next.js commonly read content from the REST API (or from GraphQL through a plugin) and render it elsewhere. That makes careful field selection, caching and preview authentication important; our guide to using WordPress as a headless CMS covers the architecture.

AI tooling is the newest consumer. The Abilities API in 6.9 exposes registered abilities through REST endpoints under wp-abilities/v1 when an ability opts in, and the MCP Adapter maps abilities to tools for AI agents, typically authenticating with application passwords. The same rules apply: give agents a limited user, expose only what they need, and rely on each ability's permission_callback.

Debugging tips

  • rest_no_route (404): check the namespace and route spelling, confirm registration runs on rest_api_init, and flush permalinks if /wp-json/ itself returns 404.
  • rest_forbidden or rest_cookie_invalid_nonce: the nonce is missing, expired (by default nonces last one day) or from a cached page, or the user lacks the capability.
  • 401 with application passwords: confirm HTTPS, that the server passes the Authorization header to PHP, and that the password was not revoked.
  • Unexpected HTML in a JSON response: a PHP notice or stray output is being printed; check debug.log.

Command-line testing is easier with WP-CLI, which can call PHP functions and inspect users and options directly; see our WP-CLI guide.

Conclusion

The REST API is how modern WordPress talks to itself and to everything else. Use cookie and nonce authentication for code inside WordPress, application passwords for external clients, and a namespaced, versioned route with a real permission_callback, validated arguments and returned (not echoed) data for anything custom. Pair that with least-privilege users and prompt updates, and the API is a solid foundation for editors, headless sites and AI integrations alike. More guides are in our WordPress hub, and eSEOspace designs and secures custom endpoints through its WordPress plugin development services.

Frequently asked questions

Is the WordPress REST API enabled by default?

Yes. It has been part of core since WordPress 4.7 and the block editor depends on it, so it should not be disabled. Public content is readable without authentication; private data and all writes require an authenticated user with the right capability.

Should I use application passwords or JWT?

For most server-to-server integrations, core Application Passwords over HTTPS are the simplest supported option and need no extra plugin. Token plugins such as JWT are only worth adding when a client specifically requires that flow.

Why does my logged-in fetch request act as if I am logged out?

Cookie authentication requires a wp_rest nonce. Without the X-WP-Nonce header (or _wpnonce parameter), WordPress treats the request as unauthenticated even if the browser sends the login cookie.

Can I hide the users endpoint?

Core only lists users who have published posts to unauthenticated visitors, but some sites restrict it further with filters or require authentication for all requests via rest_authentication_errors. Test the block editor and any integrations after making such changes.

What is the difference between the REST API and admin-ajax.php?

admin-ajax.php is an older, action-based endpoint that loads the admin environment. The REST API offers structured routes, HTTP methods, schemas, built-in argument validation and standard authentication, so it is the recommended choice for new code.

Put this into action with eSEOspace

We help businesses grow with website design that actually performs. Explore the services behind this guide:

Book a free strategy call →

Get a FREE Audit

We'll perform a comprehensive SEO, AEO, GEO & CRO audit of your website — completely free — and show you exactly how to outrank your competitors.

Don't have a site yet? Get in touch →

Get a FREE GEO/AEO/SEO Audit

We'll analyze your site's SEO, GEO, AEO & CRO — completely free — and show you exactly how to get found across Google and AI answers.

Don't have a site yet? Get in touch →

You Might Also like to Read