diff --git a/x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts b/x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts
new file mode 100644
index 0000000000000..02677dcb107c2
--- /dev/null
+++ b/x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema } from '@kbn/config-schema';
+
+export const startDatafeedSchema = schema.object({
+  start: schema.maybe(schema.oneOf([schema.number(), schema.string()])),
+  end: schema.maybe(schema.oneOf([schema.number(), schema.string()])),
+  timeout: schema.maybe(schema.any()),
+});
+
+export const datafeedConfigSchema = schema.object({
+  datafeed_id: schema.maybe(schema.string()),
+  feed_id: schema.maybe(schema.string()),
+  aggregations: schema.maybe(schema.any()),
+  aggs: schema.maybe(schema.any()),
+  chunking_config: schema.maybe(schema.any()),
+  frequency: schema.maybe(schema.string()),
+  indices: schema.arrayOf(schema.string()),
+  indexes: schema.maybe(schema.arrayOf(schema.string())),
+  job_id: schema.maybe(schema.string()),
+  query: schema.maybe(schema.any()),
+  max_empty_searches: schema.maybe(schema.number()),
+  query_delay: schema.maybe(schema.string()),
+  script_fields: schema.maybe(schema.any()),
+  scroll_size: schema.maybe(schema.number()),
+  delayed_data_check_config: schema.maybe(schema.any()),
+});
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts b/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts
index 5917ec50884d8..5da825a905e8d 100644
--- a/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts
+++ b/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts
@@ -6,6 +6,7 @@
 
 import { schema } from '@kbn/config-schema';
 import { anomalyDetectionJobSchema } from './anomaly_detectors_schema';
+import { datafeedConfigSchema } from './datafeeds_schema';
 
 export const estimateBucketSpanSchema = schema.object({
   aggTypes: schema.arrayOf(schema.nullable(schema.string())),
@@ -38,22 +39,6 @@ export const validateJobSchema = schema.object({
   job: schema.object(anomalyDetectionJobSchema),
 });
 
-const datafeedConfigSchema = schema.object({
-  datafeed_id: schema.string(),
-  aggregations: schema.maybe(schema.any()),
-  aggs: schema.maybe(schema.any()),
-  chunking_config: schema.maybe(schema.any()),
-  frequency: schema.maybe(schema.string()),
-  indices: schema.arrayOf(schema.string()),
-  indexes: schema.maybe(schema.arrayOf(schema.string())),
-  job_id: schema.string(),
-  query: schema.any(),
-  query_delay: schema.maybe(schema.string()),
-  script_fields: schema.maybe(schema.any()),
-  scroll_size: schema.maybe(schema.number()),
-  delayed_data_check_config: schema.maybe(schema.any()),
-});
-
 export const validateCardinalitySchema = {
   ...anomalyDetectionJobSchema,
   datafeed_config: datafeedConfigSchema,
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts
index e006ad3d3718f..10961182be841 100644
--- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts
+++ b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts
@@ -27,7 +27,6 @@ import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status'
 import { LICENSE_TYPE } from '../../common/constants/license';
 import { annotationRoutes } from '../routes/annotations';
 import { jobRoutes } from '../routes/anomaly_detectors';
-// @ts-ignore: could not find declaration file for module
 import { dataFeedRoutes } from '../routes/datafeeds';
 // @ts-ignore: could not find declaration file for module
 import { indicesRoutes } from '../routes/indices';
diff --git a/x-pack/legacy/plugins/ml/server/routes/apidoc.json b/x-pack/legacy/plugins/ml/server/routes/apidoc.json
index 89751abdbe20d..7d1f13ead3fef 100644
--- a/x-pack/legacy/plugins/ml/server/routes/apidoc.json
+++ b/x-pack/legacy/plugins/ml/server/routes/apidoc.json
@@ -94,6 +94,17 @@
     "ValidateCardinality",
     "ValidateJob",
     "NotificationSettings",
-    "GetNotificationSettings"
+    "GetNotificationSettings",
+    "DatafeedService",
+    "GetDatafeeds",
+    "GetDatafeed",
+    "GetDatafeedsStats",
+    "GetDatafeedStats",
+    "CreateDatafeed",
+    "UpdateDatafeed",
+    "DeleteDatafeed",
+    "StartDatafeed",
+    "StopDatafeed",
+    "PreviewDatafeed"
   ]
 }
diff --git a/x-pack/legacy/plugins/ml/server/routes/datafeeds.js b/x-pack/legacy/plugins/ml/server/routes/datafeeds.js
deleted file mode 100644
index daa83795ff7d2..0000000000000
--- a/x-pack/legacy/plugins/ml/server/routes/datafeeds.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { callWithRequestFactory } from '../client/call_with_request_factory';
-import { wrapError } from '../client/errors';
-
-export function dataFeedRoutes({ commonRouteConfig, elasticsearchPlugin, route }) {
-  route({
-    method: 'GET',
-    path: '/api/ml/datafeeds',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      return callWithRequest('ml.datafeeds').catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'GET',
-    path: '/api/ml/datafeeds/{datafeedId}',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      return callWithRequest('ml.datafeeds', { datafeedId }).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'GET',
-    path: '/api/ml/datafeeds/_stats',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      return callWithRequest('ml.datafeedStats').catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'GET',
-    path: '/api/ml/datafeeds/{datafeedId}/_stats',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      return callWithRequest('ml.datafeedStats', { datafeedId }).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'PUT',
-    path: '/api/ml/datafeeds/{datafeedId}',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      const body = request.payload;
-      return callWithRequest('ml.addDatafeed', { datafeedId, body }).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'POST',
-    path: '/api/ml/datafeeds/{datafeedId}/_update',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      const body = request.payload;
-      return callWithRequest('ml.updateDatafeed', { datafeedId, body }).catch(resp =>
-        wrapError(resp)
-      );
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'DELETE',
-    path: '/api/ml/datafeeds/{datafeedId}',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const options = {
-        datafeedId: request.params.datafeedId,
-      };
-      const force = request.query.force;
-      if (force !== undefined) {
-        options.force = force;
-      }
-      return callWithRequest('ml.deleteDatafeed', options).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'POST',
-    path: '/api/ml/datafeeds/{datafeedId}/_start',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      const start = request.payload.start;
-      const end = request.payload.end;
-      return callWithRequest('ml.startDatafeed', { datafeedId, start, end }).catch(resp =>
-        wrapError(resp)
-      );
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'POST',
-    path: '/api/ml/datafeeds/{datafeedId}/_stop',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      return callWithRequest('ml.stopDatafeed', { datafeedId }).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-
-  route({
-    method: 'GET',
-    path: '/api/ml/datafeeds/{datafeedId}/_preview',
-    handler(request) {
-      const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
-      const datafeedId = request.params.datafeedId;
-      return callWithRequest('ml.datafeedPreview', { datafeedId }).catch(resp => wrapError(resp));
-    },
-    config: {
-      ...commonRouteConfig,
-    },
-  });
-}
diff --git a/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts b/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts
new file mode 100644
index 0000000000000..9335403616cf7
--- /dev/null
+++ b/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts
@@ -0,0 +1,320 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema } from '@kbn/config-schema';
+import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { wrapError } from '../client/error_wrapper';
+import { RouteInitialization } from '../new_platform/plugin';
+import { startDatafeedSchema, datafeedConfigSchema } from '../new_platform/datafeeds_schema';
+
+/**
+ * Routes for datafeed service
+ */
+export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {get} /api/ml/datafeeds Get all datafeeds
+   * @apiName GetDatafeeds
+   * @apiDescription Retrieves configuration information for datafeeds
+   */
+  router.get(
+    {
+      path: '/api/ml/datafeeds',
+      validate: false,
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds');
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {get} /api/ml/datafeeds/:datafeedId Get datafeed for given datafeed id
+   * @apiName GetDatafeed
+   * @apiDescription Retrieves configuration information for datafeed
+   */
+  router.get(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds', { datafeedId });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {get} /api/ml/datafeeds/_stats Get stats for all datafeeds
+   * @apiName GetDatafeedsStats
+   * @apiDescription Retrieves usage information for datafeeds
+   */
+  router.get(
+    {
+      path: '/api/ml/datafeeds/_stats',
+      validate: false,
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats');
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {get} /api/ml/datafeeds/:datafeedId/_stats Get datafeed stats for given datafeed id
+   * @apiName GetDatafeedStats
+   * @apiDescription Retrieves usage information for datafeed
+   */
+  router.get(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}/_stats',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats', {
+          datafeedId,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {put} /api/ml/datafeeds/:datafeedId Creates datafeed
+   * @apiName CreateDatafeed
+   * @apiDescription Instantiates a datafeed
+   */
+  router.put(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+        body: datafeedConfigSchema,
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.addDatafeed', {
+          datafeedId,
+          body: request.body,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {post} /api/ml/datafeeds/:datafeedId/_update Updates datafeed for given datafeed id
+   * @apiName UpdateDatafeed
+   * @apiDescription Updates certain properties of a datafeed
+   */
+  router.post(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}/_update',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+        body: datafeedConfigSchema,
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.updateDatafeed', {
+          datafeedId,
+          body: request.body,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {delete} /api/ml/datafeeds/:datafeedId Deletes datafeed
+   * @apiName DeleteDatafeed
+   * @apiDescription Deletes an existing datafeed
+   */
+  router.delete(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+        query: schema.maybe(schema.object({ force: schema.maybe(schema.any()) })),
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const options: { datafeedId: string; force?: boolean } = {
+          datafeedId: request.params.jobId,
+        };
+        const force = request.query.force;
+        if (force !== undefined) {
+          options.force = force;
+        }
+
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.deleteDatafeed', options);
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {post} /api/ml/datafeeds/:datafeedId/_start Starts datafeed for given datafeed id(s)
+   * @apiName StartDatafeed
+   * @apiDescription Starts one or more datafeeds
+   */
+  router.post(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}/_start',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+        body: startDatafeedSchema,
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const { start, end } = request.body;
+
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.startDatafeed', {
+          datafeedId,
+          start,
+          end,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {post} /api/ml/datafeeds/:datafeedId/_stop Stops datafeed for given datafeed id(s)
+   * @apiName StopDatafeed
+   * @apiDescription Stops one or more datafeeds
+   */
+  router.post(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}/_stop',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.stopDatafeed', {
+          datafeedId,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+
+  /**
+   * @apiGroup DatafeedService
+   *
+   * @api {get} /api/ml/datafeeds/:datafeedId/_preview Preview datafeed for given datafeed id
+   * @apiName PreviewDatafeed
+   * @apiDescription Previews a datafeed
+   */
+  router.get(
+    {
+      path: '/api/ml/datafeeds/{datafeedId}/_preview',
+      validate: {
+        params: schema.object({ datafeedId: schema.string() }),
+      },
+    },
+    licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+      try {
+        const datafeedId = request.params.datafeedId;
+        const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedPreview', {
+          datafeedId,
+        });
+
+        return response.ok({
+          body: resp,
+        });
+      } catch (e) {
+        return response.customError(wrapError(e));
+      }
+    })
+  );
+}