add_rewrite_endpointを使えば楽にできるがよりカスタマイズできるように自作でやってみる。

まずリライトルールに任意のURLを追加する。

リライトルールとはフロントコントローラが受け取った PATH_INFO を内部で query string のように扱う対応を指定しておくもの。
wp-cli で $ wp rewrite list すると、パスがどのようにクエリパラメータ扱いになるかがわかる。

add_filter('rewrite_rules_array', function($rules) {
  $rules = array_merge($rules, [
    'api/?$'       => 'index.php?original-api=1',
    'api/([^/]+?)' => 'index.php?original-api=1&category_name=$matches[1]',
  ]);
  return $rules;
});

WordPressは登録していないパラメーターは無視するため、下記のようにquery_vars フィルターに追加する。

add_filter('query_vars', function ($vars){
  $vars[] = 'original-api';
  return $vars;
});

リライトルールに実際の query string をマージし、得られたパラメータから最終的に1つの WP_Query object を作成する。
これをメインクエリーと呼ぶ。

この WP_Query オブジェクト(global $wp_query に保持されるもの)が作られた後、テンプレート階層に従って選ばれたテーマ内のテンプレートファイルに渡され、HTML が表示されることになる。
しかし今回のエンドポイントはそれに該当しないため、フックでルーティングを変更する必要がある。

add_action('template_redirect', function(){
    global $wp_query;
    if ($wp_query->get('custom-api')) {
        renderCustomAPI($wp_query->posts);
    }
});

// 描画
function renderCustomAPI($posts){
  $res = [];
  foreach ($posts as $post) {
    $res[] = [
      'id' => $post->ID,
      'content' = apply_filters('the_content', $post->post_content),
    ];
  }

  header('Content-Type: application/json;charset=utf-8');
  echo json_encode($res);
  exit;
}

以上で設定完了だが、リライトルールの変更はデータベースにアクセスする処理になるため、コードを書いただけでは発火されない。

管理画面のパーマリンクの設定を変更せずに一度保存してもらうか、wp-cli だと $ wp rewrite flush のようなコマンドでリライトルールを変更できる。