A lot of custom REST endpoints in WordPress plugins skip argument validation entirely, trusting whatever gets sent in the request body. That works fine until someone sends a malformed request, intentionally or not, and the endpoint either throws an ugly fatal error or, worse, silently accepts bad data.

Registering a route with real validation

add_action('rest_api_init', function() {
    register_rest_route('myplugin/v1', '/orders/(?P<id>\d+)', [
        'methods'  => 'GET',
        'callback' => 'myplugin_get_order',
        'permission_callback' => function() {
            return current_user_can('manage_woocommerce');
        },
        'args' => [
            'id' => [
                'validate_callback' => function($param) {
                    return is_numeric($param);
                },
                'sanitize_callback' => 'absint',
                'required' => true,
            ],
        ],
    ]);
});

The validate_callback and sanitize_callback run automatically before your actual callback function is even called — if validation fails, WordPress returns a 400 error on its own, and your callback function never has to deal with malformed input at all.

permission_callback isn’t optional

Leaving it out (or returning true unconditionally, which is a common shortcut during development that sometimes ships to production by accident) means the endpoint is publicly accessible to anyone who finds the URL, logged in or not. It’s easy to overlook because the endpoint still “works” during testing when you’re logged in as admin.

The callback itself

function myplugin_get_order($request) {
    $id = $request->get_param('id');
    $order = wc_get_order($id);
    if (!$order) {
        return new WP_Error('not_found', 'Order not found', ['status' => 404]);
    }
    return rest_ensure_response([
        'id' => $order->get_id(),
        'status' => $order->get_status(),
        'total' => $order->get_total(),
    ]);
}

Returning a WP_Error with an explicit status code is what makes the API predictable for whatever’s consuming it — a frontend app checking for a 404 shouldn’t have to guess based on response shape.