Index: src/wp-includes/rest-api.php
===================================================================
--- src/wp-includes/rest-api.php	(revision 44545)
+++ src/wp-includes/rest-api.php	(working copy)
@@ -17,7 +17,10 @@
 /**
  * Registers a REST API route.
  *
+ * Note: Do not use before the {@see 'rest_api_init'} hook.
+ *
  * @since 4.4.0
+ * @since 5.1.0 Added a _doing_it_wrong() notice when not called on the rest_api_init hook.
  *
  * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
  * @param string $route     The base URL for route you are adding.
@@ -41,6 +44,10 @@
 		return false;
 	}
 
+	if ( ! did_action( 'rest_api_init' ) && ! doing_action( 'rest_api_init' ) ) {
+		_doing_it_wrong( 'register_rest_route', __( 'REST API routes must be registered on the rest_api_init action.' ), '5.1.0' );
+	}
+
 	if ( isset( $args['args'] ) ) {
 		$common_args = $args['args'];
 		unset( $args['args'] );
Index: tests/phpunit/tests/rest-api.php
===================================================================
--- tests/phpunit/tests/rest-api.php	(revision 44545)
+++ tests/phpunit/tests/rest-api.php	(working copy)
@@ -75,6 +75,8 @@
 	 * Check that a single route is canonicalized.
 	 *
 	 * Ensures that single and multiple routes are handled correctly.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_canonicalized() {
 		register_rest_route(
@@ -110,6 +112,8 @@
 	 * Check that a single route is canonicalized.
 	 *
 	 * Ensures that single and multiple routes are handled correctly.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_canonicalized_multiple() {
 		register_rest_route(
@@ -151,6 +155,8 @@
 
 	/**
 	 * Check that routes are merged by default.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_merge() {
 		register_rest_route(
@@ -178,6 +184,8 @@
 
 	/**
 	 * Check that we can override routes.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_override() {
 		register_rest_route(
@@ -256,6 +264,9 @@
 		$this->assertTrue( in_array( 'rest_route', $GLOBALS['wp']->public_query_vars ) );
 	}
 
+	/**
+	 * @expectedIncorrectUsage register_rest_route
+	 */
 	public function test_route_method() {
 		register_rest_route(
 			'test-ns',
@@ -273,6 +284,8 @@
 
 	/**
 	 * The 'methods' arg should accept a single value as well as array.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_method_string() {
 		register_rest_route(
@@ -291,6 +304,8 @@
 
 	/**
 	 * The 'methods' arg should accept a single value as well as array.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_method_array() {
 		register_rest_route(
@@ -315,6 +330,8 @@
 
 	/**
 	 * The 'methods' arg should a comma seperated string.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_route_method_comma_seperated() {
 		register_rest_route(
@@ -337,6 +354,9 @@
 		);
 	}
 
+	/**
+	 * @expectedIncorrectUsage register_rest_route
+	 */
 	public function test_options_request() {
 		register_rest_route(
 			'test-ns',
@@ -358,6 +378,8 @@
 
 	/**
 	 * Ensure that the OPTIONS handler doesn't kick in for non-OPTIONS requests.
+	 *
+	 * @expectedIncorrectUsage register_rest_route
 	 */
 	public function test_options_request_not_options() {
 		register_rest_route(
@@ -696,6 +718,9 @@
 		return 'Spy_REST_Server';
 	}
 
+	/**
+	 * @expectedIncorrectUsage register_rest_route2
+	 */
 	public function test_register_rest_route_without_server() {
 		$GLOBALS['wp_rest_server'] = null;
 		add_filter( 'wp_rest_server_class', array( $this, 'filter_wp_rest_server_class' ) );
