ルートとエンドポイント

2020.03.26 2020.03.26

TOPICS

翻訳元記事はこちらです。

概要

REST API は、WordPress インストール内の様々なリソースに URI をマッチさせる方法を提供してくれます。デフォルトでは、整ったパーマリンクを有効にしている場合、WordPress REST API は /wp-json/ に “住んでいます”。
私たちのWordPressサイト https://ourawesomesite.com では、https://ourawesomesite.com/wp-json/GETリクエストをすることで、REST APIのインデックスにアクセスすることができます。
このインデックスでは、特定の WordPress インストールで利用可能なルート、サポートされている HTTP メソッド、登録されているエンドポイントなどの情報を提供しています。

ルートかエンドポイントか

エンドポイントとは、APIを通じて利用できる機能のことです。これは、API インデックスの取得、投稿の更新、コメントの削除などです。
エンドポイントは特定の機能を実行し、いくつかのパラメータを取り、クライアントにデータを返します。

ルートは、エンドポイントにアクセスするために使用する「名前」であり、URLで使用されます。ルートは複数のエンドポイントを関連付けることができ、どのエンドポイントを使用するかは HTTP メソッドに依存します。

http://example.com/wp-json/wp/v2/posts/123を例に説明します。

ルートは wp/v2/posts/123 – wp-json は API 自体のベースパスなので、ルートには wp-json が含まれていません。

整ったパーマリンクがないサイトでは、ルートは代わりに rest_route パラメータとして URL に追加されます。上記の例では、完全な URL は http://example.com/?rest_route=wp/v2/posts/123 となります。

エンドポイントを作成する

GETリクエストを受けたときに「Hello World, this is the WordPress REST API」というフレーズを返すエンドポイントを作成したい場合、まずそのエンドポイントのルートを登録する必要があります。
ルートを登録するには、register_rest_route()関数を使用しなければなりません。register_rest_route() は、エンドポイントへのルートのマッピングをすべて処理します。
「Hello World, this is the WordPress REST API」というフレーズを返すルートを作成してみましょう。

/**
 * WP_REST_Response にフレーズを埋め込むコールバック関数
 */
function prefix_get_endpoint_phrase() {
    // rest_ensure_response() は、返したいデータを WP_REST_Response にラップし、適切に返されるようにします。
    return rest_ensure_response( 'Hello World, this is the WordPress REST API' );
}
 
/**
 * この関数は、例のエンドポイントのルートを登録する場所です。
 */
function prefix_register_example_routes() {
    // register_rest_route() はより多くの引数を扱いますが、今のところは基本的な箇所だけ設定します。
    register_rest_route( 'hello-world/v1', '/phrase', array(
        // この定数を使用することで、WP_REST_Serverが変更されたときに、読み取り可能なエンドポイントが意図した通りに動作することを保証します。
        'methods'  => WP_REST_Server::READABLE,
        // ここでは、コールバックを登録します。このエンドポイントが WP_REST_Server クラスにマッチしたときにコールバックが発生します。
        'callback' => 'prefix_get_endpoint_phrase',
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_example_routes' );

register_rest_route() に渡される最初の引数はネームスペースです。
2番目の引数に渡されるのはリソースパス、つまりリソースベースです。

この例では、取得しようとしているリソースは「Hello World, this is the WordPress REST API」というフレーズです。
3番目の引数はオプションの配列です。ここでは、エンドポイントがどのようなメソッドを使用できるか、またエンドポイントがマッチしたときにどのようなコールバックが発生するかを指定します。
(他にもいろいろなことができますが、まずは基本的なことのみを説明します)

3番目の引数では、エンドポイントのアクセスを特定のユーザーだけに制限できるようにするパーミッションコールバックを提供することもできます。
3番目の引数は、リクエストがエンドポイントのレスポンスを変更できるように、エンドポイントの引数を登録する方法も提供します。これらの概念については、このガイドのエンドポイントのセクションで説明します。

https://ourawesomesite.com/wp-json/hello-world/v1/phrase にアクセスすると、REST API が親切に挨拶してくれます。
それでは、もう少し詳しくルートを見てみましょう。

ルート

REST API のルートは URI で表現されます。
ルート自体は https://ourawesomesite.com/wp-json の末尾に貼り付けられているものです。API のインデックスルートは '/' です。これが https://ourawesomesite.com/wp-json/ が API で利用可能なすべての情報を返す理由です。
すべてのルートはこのルートの上に構築されるべきで、wp-json の部分は変更することができますが、一般的には同じままにしておくことをお勧めします。

ルートはユニークなものにしてください。
例えば、/booksのような本のルートを設定することができます。
これで本のルートは https://ourawesomesite.com/wp-json/books になります。
しかし、API の潜在的なルートを汚染してしまうことになるので、これは良い習慣ではありません。別のプラグインで/booksのルートを登録したい場合はどうでしょうか?
その場合、2つのルートがお互いに競合してしまい、1つしか使えなくなってしまうので、大きな問題になるでしょう。
register_rest_route() の4番目のパラメータは、ルートが既存のルートをオーバーライドするかどうかのbooleanです。

両方のルートがオーバーライドされる可能性があるため、オーバーライドパラメーターは問題を解決するものではありません。
そこで、ルートに名前空間を使用することになります。

名前空間(ネームスペース)

ルートに名前空間を追加することは非常に重要です。”コア” エンドポイントは wp/v2 の名前空間を使用します。

コアにマージするつもりでエンドポイントを作っている場合以外は、何もwpネームスペースに入れないでください。

コアエンドポイントの名前空間には、注意すべき重要な点がいくつかあります。名前空間の最初の部分は wp で、これはベンダー名である WordPress を表します。
プラグインでは、名前空間のベンダー部分の名前をユニークな名前にする必要があります。上の例では hello-world を使用しています。

ベンダーの部分に続くのは、名前空間のバージョンの部分です。”コア”エンドポイントは、WordPress REST API のバージョン 2 を表すために v2 を利用しています。
プラグインを書いている場合は、新しいエンドポイントを作成し、提供するバージョン番号をアップするだけで、REST API エンドポイントの下位互換性を維持することができます。こうすることで、元の v1v2 の両方のエンドポイントにアクセスすることができます。

名前空間に続くルートの部分がリソースパスになります。

リソースパス

リソースパスは、エンドポイントがどのリソースに関連付けられているかを示す必要があります。
上で使用した例では、対話しているリソースがフレーズであることを示すためにphraseという単語を使用しました。衝突を避けるために、登録する各リソースパスも名前空間内で一意でなければなりません。リソースパスは、与えられた名前空間内で異なるリソースルートを定義するために使用する必要があります。

基本的なeコマース機能を扱うプラグインがあるとしましょう。リソースには、注文と商品の2つのタイプがあります。注文は製品のリクエストですが、製品そのものではありません。商品にも同じ概念が適用されます。これらのリソースは関連していますが、同じものではありません。
私たちのルートは、私たちのeCommerceプラグインの場合、次のようになります。/my-shop/v1/order/my-shop/v1/products です。

このようなルートを使用して、それぞれが注文や製品のコレクションを返すようにしたいと思います。特定の製品を ID で取得したい場合はどうでしょうか?

パス変数

パス変数を使用することで、動的なルートを追加することができます。eコマースのルートを拡張するために、個々の製品を取得するルートを登録することができます。

/**
 * 製品一覧を返すコールバック関数
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function prefix_get_products( $request ) {
    // In practice this function would fetch the desired data. Here we are just making stuff up.
    $products = array(
        '1' => 'I am product 1',
        '2' => 'I am product 2',
        '3' => 'I am product 3',
    );
 
    return rest_ensure_response( $products );
}
 
/**
 * 特定の製品を返すコールバック関数
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function prefix_get_product( $request ) {
    // In practice this function would fetch the desired data. Here we are just making stuff up.
    $products = array(
        '1' => 'I am product 1',
        '2' => 'I am product 2',
        '3' => 'I am product 3',
    );
 
    // Here we are grabbing the 'id' path variable from the $request object. WP_REST_Request implements ArrayAccess, which allows us to grab properties as though it is an array.
    $id = (string) $request['id'];
 
    if ( isset( $products[ $id ] ) ) {
        // Grab the product.
        $product = $products[ $id ];
 
        // Return the product as a response.
        return rest_ensure_response( $product );
    } else {
        // Return a WP_Error because the request product was not found. In this case we return a 404 because the main resource was not found.
        return new WP_Error( 'rest_product_invalid', esc_html__( 'The product does not exist.', 'my-text-domain' ), array( 'status' => 404 ) );
    }
 
    // If the code somehow executes to here something bad happened return a 500.
    return new WP_Error( 'rest_api_sad', esc_html__( 'Something went horribly wrong.', 'my-text-domain' ), array( 'status' => 500 ) );
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_product_routes() {
    // Here we are registering our route for a collection of products.
    register_rest_route( 'my-shop/v1', '/products', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_products',
    ) );
    // Here we are registering our route for single products. The (?P<id>[\d]+) is our path variable for the ID, which, in this example, can only be some form of positive number.
    register_rest_route( 'my-shop/v1', '/products/(?P<id>[\d]+)', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_product',
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_product_routes' );

上記の例は多くのことをカバーしています。重要なのは、2番目に登録するルートで、リソースパス/productsにパス変数/(?P[\d]+)を追加していることです。パス変数は正規表現です。ここでは、任意の数字であることを示すために、[\d]+を使用しています。
リソースに数字のIDを使用している場合、これはパス変数の使用方法の良い例です。パス変数を使用する際には、ユーザ入力なので、何がマッチするかに注意しなければなりません。

正規表現は幸いにも数値以外のものはフィルタリングしてくれます。しかし、要求されたIDの製品が存在しない場合は、エラー処理を行う必要があります。
上のコード例でエラー処理の基本的な方法を見ることができます。エンドポイントのコールバックで WP_Error を返すと、API サーバーは自動的にクライアントにエラーを返します。

このセクションではルートについて説明しますが、エンドポイントについてはかなりの部分をカバーしています。エンドポイントとルートは相互に関連していますが、明確に区別されています。

エンドポイント

エンドポイントとは、ルートがマップする必要のある行き先のことです。
任意のルートに対して、複数の異なるエンドポイントを登録することができます。
ルートとエンドポイントの区別をよりよく示すために、架空のeCommerceプラグインを拡張してみましょう。ここでは、/wp-json/my-shop/v1/products/ルートに存在する2つのエンドポイントを作成します。
1つのエンドポイントはHTTPのGETメソッドを使って商品を取得し、もう1つのエンドポイントはHTTPのPOSTメソッドを使って新しい商品を作成します。

/**
 * 製品一覧を返すコールバック関数
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function prefix_get_products( $request ) {
    // In practice this function would fetch the desired data. Here we are just making stuff up.
    $products = array(
        '1' => 'I am product 1',
        '2' => 'I am product 2',
        '3' => 'I am product 3',
    );
 
    return rest_ensure_response( $products );
}
 
/**
 * 製品を作成するコールバック関数
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function prefix_create_product( $request ) {
    // 実際には、この関数は製品を作成します。ここでは省略します。
   return rest_ensure_response( 'Product has been created' );
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_product_routes() {
    // Here we are registering our route for a collection of products and creation of products.
    register_rest_route( 'my-shop/v1', '/products', array(
        array(
            // By using this constant we ensure that when the WP_REST_Server changes, our readable endpoints will work as intended.
            'methods'  => WP_REST_Server::READABLE,
            // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
            'callback' => 'prefix_get_products',
        ),
        array(
            // By using this constant we ensure that when the WP_REST_Server changes, our create endpoints will work as intended.
            'methods'  => WP_REST_Server::CREATABLE,
            // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
            'callback' => 'prefix_create_product',
        ),
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_product_routes' );

ルート /wp-json/my-shop/v1/products にどの HTTP メソッドを使用するかによって、異なるエンドポイントにマッチし、異なるコールバックが発生します。POST を使用した場合は prefix_create_products() コールバックを、GET を使用した場合は prefix_get_products()コールバックを発行します。

HTTP メソッドにはいくつかの種類があり、REST API はそれらのいずれかを利用することができます。

HTTPメソッド

HTTP メソッドは HTTP 動詞と呼ばれることもあります。これらは単に HTTP 経由で通信するための異なる方法です。WordPress REST API で使われている主なものは以下の通りです。

これらのメソッドは HTTP 1.1 で導入されたため、すべてのクライアントでサポートされているわけではないことに注意することが重要です。
幸いなことに、API はこれらの不幸なケースを回避する方法を提供しています。リソースを削除したいが DELETE リクエストを送信できない場合は、_method パラメータか X-HTTP-Method-Override ヘッダをリクエストに使用することができます。
これがどのように動作するかというと、https://ourawesomesite.com/wp-json/my-shop/v1/products/1?_method=DELETEPOST リクエストを送信します。
クライアントがリクエストで適切な HTTP メソッドを送信できなかったか、あるいは DELETE リクエストをブロックするファイアウォールがあったのかもしれませんが、これで製品番号 1 が削除されました。

HTTP メソッドは、ルートとコールバックの組み合わせで、エンドポイントのコアを構成しています。

コールバック

現在、REST API でサポートされているエンドポイントのコールバックには、callbackpermission_callback の 2 種類しかありません。
メインのコールバックは、リソースとのやりとりを処理しなければなりません。permissions コールバックは、エンドポイントへのアクセス権を持つユーザーを処理しなければなりません。
エンドポイントを登録する際に追加情報を追加することで、追加のコールバックを追加することができます。そして、rest_pre_dispatch フック、rest_dispatch_request フック、rest_post_dispatchフックにフックして、新しいカスタムコールバックを発行することができます。

エンドポイントコールバック

deleteエンドポイントのメインコールバックは、リソースを削除してそのコピーをレスポンスで返すだけです。
createエンドポイントのメインコールバックは、リソースのみを作成し、新しく作成されたデータにマッチするレスポンスを返すべきです。
updateコールバックは、実際に存在するリソースのみを変更する必要があります。
readコールバックは、すでに存在するデータのみを取得する必要があります。べある操作を1回行っても、複数回行っても結果が同じであることが重要です。

REST API のコンテキストでは、冪等性(べきとうせい)とは、エンドポイントに同じリクエストをした場合、サーバーは同じようにリクエストを処理することを意味します。
readエンドポイントに冪等性が保たれていない場合はどうでしょう?
データを取得しようとしているだけなのに、リクエストを行うたびにサーバーの状態が変更されてしまいます。これは壊滅的な事態になりかねません。
誰かがサーバからデータを取得するたびに、内部的に何かが変化してしまうのです。
読み取り、更新、削除のエンドポイントが不快な副作用をもたらさないように、意図したことだけを実行するようにすることが重要です。

REST API では、冪等性はエンドポイントコールバックではなく HTTP メソッドに結び付けられています。
GET, HEAD, TRACE, OPTIONS, PUT, DELETE を使用したコールバックであれば、副作用は発生しないはずです。
POST リクエストは冪等性はなく、通常はリソースを作成するために使用されます。
もしあなたが冪等性のあるcreateメソッドを作成した場合、同じリクエストをしてもサーバへの副作用がなくなるため、リソースは1つしか作成されません。
createについては、同じリクエストを何度も繰り返した場合、サーバは毎回新しいリソースを生成しなければなりません。

エンドポイントの利用を制限するには、パーミッションコールバックを登録する必要があります。

パーミッションコールバック

パーミッションコールバックは、WordPress REST API を利用したセキュリティのために非常に重要です。公開してはいけないプライベートなデータがある場合は、エンドポイントにパーミッションコールバックを登録しておく必要があります。以下は、パーミッションコールバックの登録方法の例です。

/**
 * This is our callback function that embeds our resource in a WP_REST_Response
 */
function prefix_get_private_data() {
    // rest_ensure_response() wraps the data we want to return into a WP_REST_Response, and ensures it will be properly returned.
    return rest_ensure_response( 'This is private data.' );
}
 
/**
 * This is our callback function that embeds our resource in a WP_REST_Response
 */
function prefix_get_private_data_permissions_check() {
    // Restrict endpoint to only users who have the edit_posts capability.
    if ( ! current_user_can( 'edit_posts' ) ) {
        return new WP_Error( 'rest_forbidden', esc_html__( 'OMG you can not view private data.', 'my-text-domain' ), array( 'status' => 401 ) );
    }
 
    // This is a black-listing approach. You could alternatively do this via white-listing, by returning false here and changing the permissions check.
    return true;
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_example_routes() {
    // register_rest_route() handles more arguments but we are going to stick to the basics for now.
    register_rest_route( 'my-plugin/v1', '/private-data', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_private_data',
        // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
        'permission_callback' => 'prefix_get_private_data_permissions_check',
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_example_routes' );

認証を有効にしていない状態でこのエンドポイントを試してみると、エラー・レスポンスが返され、データを見ることができなくなります。
認証は非常に大きなトピックであり、最終的にはこの章の一部を作成して、独自の認証プロセスを作成する方法を示します。

引数

エンドポイントへのリクエストを行う際に、レスポンスを変更するために追加のパラメーターを指定する必要があるかもしれません。これらの余分なパラメーターは、エンドポイントを登録する際に追加することができます。エンドポイントでの引数の使い方の例を見てみましょう。

/**
 * This is our callback function that embeds our resource in a WP_REST_Response
 */
function prefix_get_colors( $request ) {
    // In practice this function would fetch the desired data. Here we are just making stuff up.
    $colors = array(
        'blue',
        'blue',
        'red',
        'red',
        'green',
        'green',
    );
 
    if ( isset( $request['filter'] ) ) {
       $filtered_colors = array();
       foreach ( $colors as $color ) {
           if ( $request['filter'] === $color ) {
               $filtered_colors[] = $color;
           }
       }
       return rest_ensure_response( $filtered_colors );
    }
    return rest_ensure_response( $colors );
}
 
/**
 * We can use this function to contain our arguments for the example product endpoint.
 */
function prefix_get_color_arguments() {
    $args = array();
    // Here we are registering the schema for the filter argument.
    $args['filter'] = array(
        // description should be a human readable description of the argument.
        'description' => esc_html__( 'The filter parameter is used to filter the collection of colors', 'my-text-domain' ),
        // type specifies the type of data that the argument should be.
        'type'        => 'string',
        // enum specified what values filter can take on.
        'enum'        => array( 'red', 'green', 'blue' ),
    );
    return $args;
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_example_routes() {
    // register_rest_route() handles more arguments but we are going to stick to the basics for now.
    register_rest_route( 'my-colors/v1', '/colors', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_colors',
        // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
        'args' => prefix_get_color_arguments(),
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_example_routes' );

今回の例では、フィルタの引数を指定しました。
エンドポイントをリクエストする際に、クエリパラメータとして引数を指定することができます。
https://ourawesomesitem.com/my-colors/v1/colors?filter=blueGET リクエストを行うと、コレクションの青い色だけが返されます。また、これらをクエリ文字列ではなく、リクエスト本文のボディパラメータとして渡すこともできます。
クエリパラメータとボディパラメータの違いを理解するには、HTTP の仕様を読む必要があります。
クエリパラメータは URL に貼り付けられたクエリ文字列の中に存在し、ボディパラメータは HTTP リクエストのボディに直接埋め込まれます。

エンドポイントの引数を作成しましたが、その引数が文字列であることを確認し、値が赤、緑、青のいずれかに一致するかどうかを確認するにはどうしたらよいでしょうか。
これを行うには、引数の検証コールバックを指定する必要があります。

バリデーション

API のセキュリティ上、バリデーションとサニタイズは非常に重要です。(WP 4.6以上では)
バリデートコールバックは、サニタイズコールバックの前に実行されます。
引数に validate_callback を使用して、受け取った入力が有効かどうかを確認しなければなりません。
sanitize_callback は、引数がメインコールバックで処理される前に、引数の入力を変換したり、不要な部分を削除したりするために使用します。

上の例では、フィルタパラメータが文字列であり、それが赤、緑、青のいずれかの値と一致するかどうかを確認する必要があります。validate_callback を追加した後のコードを見てみましょう。

/**
 * This is our callback function that embeds our resource in a WP_REST_Response
 */
function prefix_get_colors( $request ) {
    // In practice this function would fetch more practical data. Here we are just making stuff up.
    $colors = array(
        'blue',
        'blue',
        'red',
        'red',
        'green',
        'green',
    );
 
    if ( isset( $request['filter'] ) ) {
       $filtered_colors = array();
       foreach ( $colors as $color ) {
           if ( $request['filter'] === $color ) {
               $filtered_colors[] = $color;
           }
       }
       return rest_ensure_response( $filtered_colors );
    }
    return rest_ensure_response( $colors );
}
/**
 * Validate a request argument based on details registered to the route.
 *
 * @param  mixed            $value   Value of the 'filter' argument.
 * @param  WP_REST_Request  $request The current request object.
 * @param  string           $param   Key of the parameter. In this case it is 'filter'.
 * @return WP_Error|boolean
 */
function prefix_filter_arg_validate_callback( $value, $request, $param ) {
    // If the 'filter' argument is not a string return an error.
    if ( ! is_string( $value ) ) {
        return new WP_Error( 'rest_invalid_param', esc_html__( 'The filter argument must be a string.', 'my-text-domain' ), array( 'status' => 400 ) );
    }
 
    // Get the registered attributes for this endpoint request.
    $attributes = $request->get_attributes();
 
    // Grab the filter param schema.
    $args = $attributes['args'][ $param ];
 
    // If the filter param is not a value in our enum then we should return an error as well.
    if ( ! in_array( $value, $args['enum'], true ) ) {
        return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not one of %s' ), $param, implode( ', ', $args['enum'] ) ), array( 'status' => 400 ) );
    }
}
 
/**
 * We can use this function to contain our arguments for the example product endpoint.
 */
function prefix_get_color_arguments() {
    $args = array();
    // Here we are registering the schema for the filter argument.
    $args['filter'] = array(
        // description should be a human readable description of the argument.
        'description' => esc_html__( 'The filter parameter is used to filter the collection of colors', 'my-text-domain' ),
        // type specifies the type of data that the argument should be.
        'type'        => 'string',
        // enum specified what values filter can take on.
        'enum'        => array( 'red', 'green', 'blue' ),
        // Here we register the validation callback for the filter argument.
        'validate_callback' => 'prefix_filter_arg_validate_callback',
    );
    return $args;
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_example_routes() {
    // register_rest_route() handles more arguments but we are going to stick to the basics for now.
    register_rest_route( 'my-colors/v1', '/colors', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_colors',
        // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
        'args' => prefix_get_color_arguments(),
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_example_routes' );

サニタイズ

上の例では、enum内の値のみに入力を制限しているので、sanitize_callbackを使用する必要はありません。
厳密なバリデーションを行わず、任意の文字列をパラメータとして受け入れる場合は、sanitize_callback を登録する必要があるでしょう。
コンテンツフィールドを更新したいときに、ユーザーが alert('ZOMG Hacking you'); のようなものを入力したとします。フィールドの値は、潜在的に実行可能なスクリプトである可能性があります。
不要なデータを削除したり、データを希望の形式に変換したりするには、引数に sanitize_callback を登録する必要があります。ここでは、WordPress の sanitize_text_field() をサニタイズコールバックに使用する方法の例を示します。

/**
 * This is our callback function that embeds our resource in a WP_REST_Response.
 *
 * The parameter is already sanitized by this point so we can use it without any worries.
 */
function prefix_get_item( $request ) {
    if ( isset( $request['data'] ) ) {
        return rest_ensure_response( $request['data'] );
    }
 
    return new WP_Error( 'rest_invalid', esc_html__( 'The data parameter is required.', 'my-text-domain' ), array( 'status' => 400 ) );
}
 
/**
 * Validate a request argument based on details registered to the route.
 *
 * @param  mixed            $value   Value of the 'filter' argument.
 * @param  WP_REST_Request  $request The current request object.
 * @param  string           $param   Key of the parameter. In this case it is 'filter'.
 * @return WP_Error|boolean
 */
function prefix_data_arg_validate_callback( $value, $request, $param ) {
    // If the 'data' argument is not a string return an error.
    if ( ! is_string( $value ) ) {
        return new WP_Error( 'rest_invalid_param', esc_html__( 'The filter argument must be a string.', 'my-text-domain' ), array( 'status' => 400 ) );
    }
}
 
/**
 * Sanitize a request argument based on details registered to the route.
 *
 * @param  mixed            $value   Value of the 'filter' argument.
 * @param  WP_REST_Request  $request The current request object.
 * @param  string           $param   Key of the parameter. In this case it is 'filter'.
 * @return WP_Error|boolean
 */
function prefix_data_arg_sanitize_callback( $value, $request, $param ) {
    // It is as simple as returning the sanitized value.
    return sanitize_text_field( $value );
}
 
/**
 * We can use this function to contain our arguments for the example product endpoint.
 */
function prefix_get_data_arguments() {
    $args = array();
    // Here we are registering the schema for the filter argument.
    $args['data'] = array(
        // description should be a human readable description of the argument.
        'description' => esc_html__( 'The data parameter is used to be sanitized and returned in the response.', 'my-text-domain' ),
        // type specifies the type of data that the argument should be.
        'type'        => 'string',
        // Set the argument to be required for the endpoint.
        'required'    => true,
        // We are registering a basic validation callback for the data argument.
        'validate_callback' => 'prefix_data_arg_validate_callback',
        // Here we register the validation callback for the filter argument.
        'sanitize_callback' => 'prefix_data_arg_sanitize_callback',
    );
    return $args;
}
 
/**
 * This function is where we register our routes for our example endpoint.
 */
function prefix_register_example_routes() {
    // register_rest_route() handles more arguments but we are going to stick to the basics for now.
    register_rest_route( 'my-plugin/v1', '/sanitized-data', array(
        // By using this constant we ensure that when the WP_REST_Server changes our readable endpoints will work as intended.
        'methods'  => WP_REST_Server::READABLE,
        // Here we register our callback. The callback is fired when this endpoint is matched by the WP_REST_Server class.
        'callback' => 'prefix_get_item',
        // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
        'args' => prefix_get_data_arguments(),
    ) );
}
 
add_action( 'rest_api_init', 'prefix_register_example_routes' );

まとめ

WordPress REST API にエンドポイントを登録するための基本的なことを説明してきました。ルートとは、エンドポイントが存在する URI のことです。
エンドポイントはコールバック、メソッド、引数、その他のオプションの集合体です。
各エンドポイントは register_rest_route() を使用する際にルートにマッピングされます。
デフォルトでは、エンドポイントは様々な HTTP メソッド、メインコールバック、パーミッションコールバック、登録された引数をサポートしています。エンドポイントを登録することで、WordPress とのインタラクションのユースケースをカバーすることができます。
エンドポイントは REST API とのインタラクションの中核となるポイントとして機能しますが、この強力な API を十分に活用するためには、他にも多くのトピックを探索し、理解する必要があります。