In the context of the WordPress REST API a route is a URI which can be mapped to different HTTP methods. The mapping of an individual HTTP method to a route is known as an endpoint.

The Route is the URL path itself The Endpoint is the combination of a Route and an HTTP Method (like GET, POST, or DELETE).

/wp-json/ is a route, and when that route receives a GET request then that request is handled by the endpoint which displays what is known as the index for the WordPress REST API.

A REST API request is represented within WordPress by an instance of the WP_REST_Request class, which is used to store and retrieve information for the current request.

he WP_REST_Response class provides a way to interact with the response data returned by endpoints

Each endpoint requires a particular structure of input data, and returns data using a defined and predictable structure. Those data structures are defined in the API Schema.

Controller classes unify and coordinate all these various moving parts within a REST API response cycle. With a controller class you can manage the registration of routes & endpoints, handle requests, utilize schema, and generate API responses.


In the world of REST APIs, HAL (Hypertext Application Language) is a specific standard for defining how to represent hypermedia links within your JSON or XML responses.

Think of it as the “instruction manual” that tells a client not just what the data is, but where they can go next.

{
// example : _links
  "_links": {
    "self": { "href": "/orders/123" },
    "next": { "href": "/orders/124" },
    "customer": { "href": "/customers/654" }
  },
  "order_id": 123,
  "total": 50.00,
  "status": "shipped"
}

// example: _embedded
{
  "_links": {
    "self": { "href": "/orders/123" }
  },
  "total": 50.00,
  "_embedded": {
    "items": [
      {
        "_links": { "self": { "href": "/items/abc" } },
        "name": "Widget",
        "price": 25.00
      }
    ]
  }
}

Using a standard like HAL moves your API toward the highest level of REST Maturity (often called HATEOAS: Hypermedia as the Engine of Application State).

BenefitDescription
DecouplingThe client doesn’t need to “guess” URLs; it just follows the links provided by the server.
DiscoverabilityDevelopers can explore the API by simply clicking through the JSON links in a browser or tool.
ConsistencyIt provides a predictable pattern for every endpoint, making it easier for teams to build and consume services.
When using HAL, the Content-Type header should technically be application/hal+json.

The preferred way to handle discovery is to send a HEAD request to the supplied address. The REST API automatically adds a Link header to all front-end pages that looks like the following:

Link: <http://example.com/wp-json/>; rel="https://api.w.org/"
TypeURL ExampleDescription
Ugly (Query)example.com/?p=105Uses a database ID. Hard to remember and tells you nothing about the content.
Prettyexample.com/blog/how-to-bake-cakeUses a “slug” (keywords). Easy to read and great for SEO.

Example

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/latest-posts/(?P<id>\d+)', array(
        'methods'             => 'GET',
        'callback'            => 'my_custom_get_post_handler',
        'permission_callback' => '__return_true', // Allow public access
    ) );
} );

/**
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST_Response|WP_Error Response object or error.
 */
function my_custom_get_post_handler( $request ) {
    
    // --- 1. Working with WP_REST_Request ---
    // Extract the 'id' parameter from the URL path defined in register_rest_route
    $post_id = $request->get_param( 'id' );
    
    // You can also get headers or body data
    // $token = $request->get_header( 'Authorization' );

    $post = get_post( $post_id );

    if ( empty( $post ) ) {
        return new WP_Error( 'no_post', 'Post not found', array( 'status' => 404 ) );
    }

    // Prepare data for the response
    $data = array(
        'id'      => $post->ID,
        'title'   => $post->post_title,
        'content' => $post->post_content,
        'slug'    => get_post_field( 'post_name', $post_id ),
    );

    // --- 2. Working with WP_REST_Response ---
    $response = new WP_REST_Response( $data );
    
    // You can manually set the HTTP status code (e.g., 200 OK)
    $response->set_status( 200 );
    
    // You can add custom headers or HAL-style links
    $response->add_link( 'self', rest_url( 'my-plugin/v1/latest-posts/' . $post_id ) );
    // This is how you manually inject the "_links" section 
    $response->add_link( 'self', rest_url( 'my-plugin/v1/resource/101' ) ); 
    $response->add_link( 'collection', rest_url( 'my-plugin/v1/resource' ) ); 
    $response->add_link( 'author', rest_url( 'wp/v2/users/1' ), array( 'embeddable' => true ) );

    return $response;
}

www.example.com/wp-json wordpress rest namespace. It is suffixed with plugin specific namespace to differentiate.

/v2/posts is for default posts