diff --git a/services/azure-devops/azure-devops-coverage.service.js b/services/azure-devops/azure-devops-coverage.service.js
index c53b7a6c857022840ab334bff22422bc62cb72f2..37f72e6e0811c8cb050e0118c713fdb725271d73 100644
--- a/services/azure-devops/azure-devops-coverage.service.js
+++ b/services/azure-devops/azure-devops-coverage.service.js
@@ -1,9 +1,11 @@
 import Joi from 'joi'
+import { pathParams } from '../index.js'
 import { coveragePercentage as coveragePercentageColor } from '../color-formatters.js'
 import AzureDevOpsBase from './azure-devops-base.js'
-import { keywords } from './azure-devops-helpers.js'
 
-const documentation = `
+const description = `
+[Azure Devops](https://dev.azure.com/) (formerly VSO, VSTS) is Microsoft Azure's CI/CD platform.
+
 To obtain your own badge, you need to get 3 pieces of information:
 \`ORGANIZATION\`, \`PROJECT_ID\` and \`DEFINITION_ID\`.
 
@@ -47,33 +49,52 @@ export default class AzureDevOpsCoverage extends AzureDevOpsBase {
     pattern: ':organization/:project/:definitionId/:branch*',
   }
 
-  static examples = [
-    {
-      title: 'Azure DevOps coverage',
-      pattern: ':organization/:project/:definitionId',
-      namedParams: {
-        organization: 'swellaby',
-        project: 'opensource',
-        definitionId: '25',
+  static openApi = {
+    '/azure-devops/coverage/{organization}/{project}/{definitionId}': {
+      get: {
+        summary: 'Azure DevOps coverage',
+        description,
+        parameters: pathParams(
+          {
+            name: 'organization',
+            example: 'swellaby',
+          },
+          {
+            name: 'project',
+            example: 'opensource',
+          },
+          {
+            name: 'definitionId',
+            example: '25',
+          },
+        ),
       },
-      staticPreview: this.render({ coverage: 100 }),
-      keywords,
-      documentation,
     },
-    {
-      title: 'Azure DevOps coverage (branch)',
-      pattern: ':organization/:project/:definitionId/:branch',
-      namedParams: {
-        organization: 'swellaby',
-        project: 'opensource',
-        definitionId: '25',
-        branch: 'master',
+    '/azure-devops/coverage/{organization}/{project}/{definitionId}/{branch}': {
+      get: {
+        summary: 'Azure DevOps coverage (branch)',
+        description,
+        parameters: pathParams(
+          {
+            name: 'organization',
+            example: 'swellaby',
+          },
+          {
+            name: 'project',
+            example: 'opensource',
+          },
+          {
+            name: 'definitionId',
+            example: '25',
+          },
+          {
+            name: 'branch',
+            example: 'master',
+          },
+        ),
       },
-      staticPreview: this.render({ coverage: 100 }),
-      keywords,
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'coverage' }
 
diff --git a/services/codacy/codacy-coverage.service.js b/services/codacy/codacy-coverage.service.js
index 7d6f58081d06d88c4a4b2b3d008318132849891a..f94f3eea9b85a7415eac0f75db3ba4af5482c6e5 100644
--- a/services/codacy/codacy-coverage.service.js
+++ b/services/codacy/codacy-coverage.service.js
@@ -1,6 +1,6 @@
 import Joi from 'joi'
 import { coveragePercentage as coveragePercentageColor } from '../color-formatters.js'
-import { BaseSvgScrapingService, NotFound } from '../index.js'
+import { BaseSvgScrapingService, NotFound, pathParams } from '../index.js'
 
 const schema = Joi.object({
   message: Joi.alternatives()
@@ -12,23 +12,32 @@ export default class CodacyCoverage extends BaseSvgScrapingService {
   static category = 'coverage'
   static route = { base: 'codacy/coverage', pattern: ':projectId/:branch*' }
 
-  static examples = [
-    {
-      title: 'Codacy coverage',
-      pattern: ':projectId',
-      namedParams: { projectId: 'd5402a91aa7b4234bd1c19b5e86a63be' },
-      staticPreview: this.render({ percentage: 90 }),
+  static openApi = {
+    '/codacy/coverage/{projectId}': {
+      get: {
+        summary: 'Codacy coverage',
+        parameters: pathParams({
+          name: 'projectId',
+          example: 'd5402a91aa7b4234bd1c19b5e86a63be',
+        }),
+      },
     },
-    {
-      title: 'Codacy branch coverage',
-      pattern: ':projectId/:branch',
-      namedParams: {
-        projectId: 'd5402a91aa7b4234bd1c19b5e86a63be',
-        branch: 'master',
+    '/codacy/coverage/{projectId}/{branch}': {
+      get: {
+        summary: 'Codacy coverage (branch)',
+        parameters: pathParams(
+          {
+            name: 'projectId',
+            example: 'd5402a91aa7b4234bd1c19b5e86a63be',
+          },
+          {
+            name: 'branch',
+            example: 'master',
+          },
+        ),
       },
-      staticPreview: this.render({ percentage: 90 }),
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'coverage' }
 
diff --git a/services/codacy/codacy-grade.service.js b/services/codacy/codacy-grade.service.js
index c2a8e3f1565d2389bf2fa9fc5fc6f4f77e75ea09..ddec54c49ba8f1943f68a8854b728f02b522b6b9 100644
--- a/services/codacy/codacy-grade.service.js
+++ b/services/codacy/codacy-grade.service.js
@@ -1,5 +1,5 @@
 import Joi from 'joi'
-import { BaseSvgScrapingService } from '../index.js'
+import { BaseSvgScrapingService, pathParams } from '../index.js'
 import { codacyGrade } from './codacy-helpers.js'
 
 const schema = Joi.object({ message: codacyGrade }).required()
@@ -8,23 +8,32 @@ export default class CodacyGrade extends BaseSvgScrapingService {
   static category = 'analysis'
   static route = { base: 'codacy/grade', pattern: ':projectId/:branch*' }
 
-  static examples = [
-    {
-      title: 'Codacy grade',
-      pattern: ':projectId',
-      namedParams: { projectId: '0cb32ce695b743d68257021455330c66' },
-      staticPreview: this.render({ grade: 'A' }),
+  static openApi = {
+    '/codacy/grade/{projectId}': {
+      get: {
+        summary: 'Codacy grade',
+        parameters: pathParams({
+          name: 'projectId',
+          example: '0cb32ce695b743d68257021455330c66',
+        }),
+      },
     },
-    {
-      title: 'Codacy branch grade',
-      pattern: ':projectId/:branch',
-      namedParams: {
-        projectId: '0cb32ce695b743d68257021455330c66',
-        branch: 'master',
+    '/codacy/grade/{projectId}/{branch}': {
+      get: {
+        summary: 'Codacy grade (branch)',
+        parameters: pathParams(
+          {
+            name: 'projectId',
+            example: '0cb32ce695b743d68257021455330c66',
+          },
+          {
+            name: 'branch',
+            example: 'master',
+          },
+        ),
       },
-      staticPreview: this.render({ grade: 'A' }),
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'code quality' }
 
diff --git a/services/greasyfork/greasyfork-downloads.service.js b/services/greasyfork/greasyfork-downloads.service.js
index 73411d8b64b1c921b08e2e60fbee1fc6c8312925..9d94207c995aa7b36acb05ecfc1036ac8646be6c 100644
--- a/services/greasyfork/greasyfork-downloads.service.js
+++ b/services/greasyfork/greasyfork-downloads.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { renderDownloadsBadge } from '../downloads.js'
 import BaseGreasyForkService from './greasyfork-base.js'
 
@@ -5,20 +6,25 @@ export default class GreasyForkInstalls extends BaseGreasyForkService {
   static category = 'downloads'
   static route = { base: 'greasyfork', pattern: ':variant(dt|dd)/:scriptId' }
 
-  static examples = [
-    {
-      title: 'Greasy Fork',
-      pattern: 'dd/:scriptId',
-      namedParams: { scriptId: '407466' },
-      staticPreview: renderDownloadsBadge({ downloads: 17, interval: 'day' }),
+  static openApi = {
+    '/greasyfork/{variant}/{scriptId}': {
+      get: {
+        summary: 'Greasy Fork Downloads',
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'dt',
+            description: 'total downloads or daily downloads',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'scriptId',
+            example: '407466',
+          },
+        ),
+      },
     },
-    {
-      title: 'Greasy Fork',
-      pattern: 'dt/:scriptId',
-      namedParams: { scriptId: '407466' },
-      staticPreview: renderDownloadsBadge({ downloads: 3420 }),
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'installs' }
 
diff --git a/services/jenkins/jenkins-plugin-installs.service.js b/services/jenkins/jenkins-plugin-installs.service.js
index ce51df17180ddf5210a768d8504d542442fd06a0..40b8c9d66a501617f92bf0c0f1816812b53229a2 100644
--- a/services/jenkins/jenkins-plugin-installs.service.js
+++ b/services/jenkins/jenkins-plugin-installs.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { renderDownloadsBadge } from '../downloads.js'
 import { nonNegativeInteger } from '../validators.js'
-import { BaseJsonService, NotFound } from '../index.js'
+import { BaseJsonService, NotFound, pathParams } from '../index.js'
 
 const schemaInstallations = Joi.object()
   .keys({
@@ -29,25 +29,32 @@ export default class JenkinsPluginInstalls extends BaseJsonService {
     pattern: ':plugin/:version?',
   }
 
-  static examples = [
-    {
-      title: 'Jenkins Plugin installs',
-      pattern: ':plugin',
-      namedParams: {
-        plugin: 'view-job-filters',
+  static openApi = {
+    '/jenkins/plugin/i/{plugin}': {
+      get: {
+        summary: 'Jenkins Plugin installs',
+        parameters: pathParams({
+          name: 'plugin',
+          example: 'view-job-filters',
+        }),
       },
-      staticPreview: this.render({ installs: 10247 }),
     },
-    {
-      title: 'Jenkins Plugin installs (version)',
-      pattern: ':plugin/:version',
-      namedParams: {
-        plugin: 'view-job-filters',
-        version: '1.26',
+    '/jenkins/plugin/i/{plugin}/{version}': {
+      get: {
+        summary: 'Jenkins Plugin installs (version)',
+        parameters: pathParams(
+          {
+            name: 'plugin',
+            example: 'view-job-filters',
+          },
+          {
+            name: 'version',
+            example: '1.26',
+          },
+        ),
       },
-      staticPreview: this.render({ installs: 955 }),
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'installs' }
 
diff --git a/services/jetbrains/jetbrains-rating.service.js b/services/jetbrains/jetbrains-rating.service.js
index 3649ecd95086c97a05ba870edfbfeb20a5bcb72c..5fe197e66671d42f5f36ce6f0d032e06d41965ce 100644
--- a/services/jetbrains/jetbrains-rating.service.js
+++ b/services/jetbrains/jetbrains-rating.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { starRating } from '../text-formatters.js'
 import { colorScale } from '../color-formatters.js'
-import { NotFound } from '../index.js'
+import { NotFound, pathParams } from '../index.js'
 import JetbrainsBase from './jetbrains-base.js'
 
 const pluginRatingColor = colorScale([2, 3, 4])
@@ -38,30 +38,24 @@ export default class JetbrainsRating extends JetbrainsBase {
     pattern: ':format(rating|stars)/:pluginId',
   }
 
-  static examples = [
-    {
-      title: 'JetBrains Plugins',
-      pattern: 'rating/:pluginId',
-      namedParams: {
-        pluginId: '11941',
+  static openApi = {
+    '/jetbrains/plugin/r/{format}/{pluginId}': {
+      get: {
+        summary: 'JetBrains Plugin Rating',
+        parameters: pathParams(
+          {
+            name: 'format',
+            example: 'rating',
+            schema: { type: 'string', enum: this.getEnum('format') },
+          },
+          {
+            name: 'pluginId',
+            example: '11941',
+          },
+        ),
       },
-      staticPreview: this.render({
-        rating: '4.5',
-        format: 'rating',
-      }),
     },
-    {
-      title: 'JetBrains Plugins',
-      pattern: 'stars/:pluginId',
-      namedParams: {
-        pluginId: '11941',
-      },
-      staticPreview: this.render({
-        rating: '4.5',
-        format: 'stars',
-      }),
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'rating' }
 
diff --git a/services/librariesio/librariesio-dependent-repos.service.js b/services/librariesio/librariesio-dependent-repos.service.js
index 11581d7a261156890284a8adce24c28859c8b2db..e8b4c1765983b8d1ff9480cbe2a0f221cb8a941c 100644
--- a/services/librariesio/librariesio-dependent-repos.service.js
+++ b/services/librariesio/librariesio-dependent-repos.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { metric } from '../text-formatters.js'
 import LibrariesIoBase from './librariesio-base.js'
 
@@ -10,27 +11,42 @@ export default class LibrariesIoDependentRepos extends LibrariesIoBase {
     pattern: ':platform/:scope(@[^/]+)?/:packageName',
   }
 
-  static examples = [
-    {
-      title: 'Dependent repos (via libraries.io)',
-      pattern: ':platform/:packageName',
-      namedParams: {
-        platform: 'npm',
-        packageName: 'got',
+  static openApi = {
+    '/librariesio/dependent-repos/{platform}/{packageName}': {
+      get: {
+        summary: 'Dependent repos (via libraries.io)',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'packageName',
+            example: 'got',
+          },
+        ),
       },
-      staticPreview: this.render({ dependentReposCount: 84000 }),
     },
-    {
-      title: 'Dependent repos (via libraries.io), scoped npm package',
-      pattern: ':platform/:scope/:packageName',
-      namedParams: {
-        platform: 'npm',
-        scope: '@babel',
-        packageName: 'core',
+    '/librariesio/dependent-repos/{platform}/{scope}/{packageName}': {
+      get: {
+        summary: 'Dependent repos (via libraries.io), scoped npm package',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'scope',
+            example: '@babel',
+          },
+          {
+            name: 'packageName',
+            example: 'core',
+          },
+        ),
       },
-      staticPreview: this.render({ dependentReposCount: 50 }),
     },
-  ]
+  }
 
   static _cacheLength = 900
 
diff --git a/services/librariesio/librariesio-dependents.service.js b/services/librariesio/librariesio-dependents.service.js
index eca5d337be93b4fbf32536707e7d7aae4d5326d6..0dfaed871d1f20f5690f4a4f65b5eca617bdcaea 100644
--- a/services/librariesio/librariesio-dependents.service.js
+++ b/services/librariesio/librariesio-dependents.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { metric } from '../text-formatters.js'
 import LibrariesIoBase from './librariesio-base.js'
 
@@ -10,27 +11,42 @@ export default class LibrariesIoDependents extends LibrariesIoBase {
     pattern: ':platform/:scope(@[^/]+)?/:packageName',
   }
 
-  static examples = [
-    {
-      title: 'Dependents (via libraries.io)',
-      pattern: ':platform/:packageName',
-      namedParams: {
-        platform: 'npm',
-        packageName: 'got',
+  static openApi = {
+    '/librariesio/dependents/{platform}/{packageName}': {
+      get: {
+        summary: 'Dependents (via libraries.io)',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'packageName',
+            example: 'got',
+          },
+        ),
       },
-      staticPreview: this.render({ dependentCount: 2000 }),
     },
-    {
-      title: 'Dependents (via libraries.io), scoped npm package',
-      pattern: ':platform/:scope/:packageName',
-      namedParams: {
-        platform: 'npm',
-        scope: '@babel',
-        packageName: 'core',
+    '/librariesio/dependents/{platform}/{scope}/{packageName}': {
+      get: {
+        summary: 'Dependents (via libraries.io), scoped npm package',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'scope',
+            example: '@babel',
+          },
+          {
+            name: 'packageName',
+            example: 'core',
+          },
+        ),
       },
-      staticPreview: this.render({ dependentCount: 94 }),
     },
-  ]
+  }
 
   static _cacheLength = 900
 
diff --git a/services/librariesio/librariesio-sourcerank.service.js b/services/librariesio/librariesio-sourcerank.service.js
index 21055bf3b2dcc4becfa7be703d3c3083bd1193a4..dacc98b75e6e1555a29044dffcc7321f40fdf170 100644
--- a/services/librariesio/librariesio-sourcerank.service.js
+++ b/services/librariesio/librariesio-sourcerank.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { colorScale } from '../color-formatters.js'
 import LibrariesIoBase from './librariesio-base.js'
 
@@ -11,27 +12,42 @@ export default class LibrariesIoSourcerank extends LibrariesIoBase {
     pattern: ':platform/:scope(@[^/]+)?/:packageName',
   }
 
-  static examples = [
-    {
-      title: 'Libraries.io SourceRank',
-      pattern: ':platform/:packageName',
-      namedParams: {
-        platform: 'npm',
-        packageName: 'got',
+  static openApi = {
+    '/librariesio/sourcerank/{platform}/{packageName}': {
+      get: {
+        summary: 'Libraries.io SourceRank',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'packageName',
+            example: 'got',
+          },
+        ),
       },
-      staticPreview: this.render({ rank: 25 }),
     },
-    {
-      title: 'Libraries.io SourceRank, scoped npm package',
-      pattern: ':platform/:scope/:packageName',
-      namedParams: {
-        platform: 'npm',
-        scope: '@babel',
-        packageName: 'core',
+    '/librariesio/sourcerank/{platform}/{scope}/{packageName}': {
+      get: {
+        summary: 'Libraries.io SourceRank, scoped npm package',
+        parameters: pathParams(
+          {
+            name: 'platform',
+            example: 'npm',
+          },
+          {
+            name: 'scope',
+            example: '@babel',
+          },
+          {
+            name: 'packageName',
+            example: 'core',
+          },
+        ),
       },
-      staticPreview: this.render({ rank: 3 }),
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'sourcerank',
diff --git a/services/osslifecycle/osslifecycle.service.js b/services/osslifecycle/osslifecycle.service.js
index 3766b7c8d42d39b842a8c17b7e91b7f4632428ae..0e4f2303fa1285c9ccd3d3ce16615ad6e91adbc9 100644
--- a/services/osslifecycle/osslifecycle.service.js
+++ b/services/osslifecycle/osslifecycle.service.js
@@ -1,6 +1,6 @@
-import { BaseService, InvalidResponse } from '../index.js'
+import { BaseService, InvalidResponse, pathParams } from '../index.js'
 
-const documentation = `
+const description = `
 OSS Lifecycle is an initiative started by Netflix to classify open-source projects into lifecycles
 and clearly identify which projects are active and which ones are retired. To enable this badge,
 simply create an OSSMETADATA tagging file at the root of your GitHub repository containing a
@@ -17,28 +17,44 @@ export default class OssTracker extends BaseService {
     pattern: ':user/:repo/:branch*',
   }
 
-  static examples = [
-    {
-      title: 'OSS Lifecycle',
-      pattern: ':user/:repo',
-      namedParams: { user: 'Teevity', repo: 'ice' },
-      staticPreview: this.render({ status: 'active' }),
-      keywords: ['Netflix'],
-      documentation,
+  static openApi = {
+    '/osslifecycle/{user}/{repo}': {
+      get: {
+        summary: 'OSS Lifecycle',
+        description,
+        parameters: pathParams(
+          {
+            name: 'user',
+            example: 'Teevity',
+          },
+          {
+            name: 'repo',
+            example: 'ice',
+          },
+        ),
+      },
     },
-    {
-      title: 'OSS Lifecycle (branch)',
-      pattern: ':user/:repo/:branch',
-      namedParams: {
-        user: 'Netflix',
-        repo: 'osstracker',
-        branch: 'documentation',
+    '/osslifecycle/{user}/{repo}/{branch}': {
+      get: {
+        summary: 'OSS Lifecycle (branch)',
+        description,
+        parameters: pathParams(
+          {
+            name: 'user',
+            example: 'Netflix',
+          },
+          {
+            name: 'repo',
+            example: 'osstracker',
+          },
+          {
+            name: 'branch',
+            example: 'documentation',
+          },
+        ),
       },
-      staticPreview: this.render({ status: 'active' }),
-      keywords: ['Netflix'],
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'oss lifecycle' }
 
diff --git a/services/travis/travis-build.service.js b/services/travis/travis-build.service.js
index 706d998394502c47af627ef5b8749769dd54c765..af7dfc44d9631603753007209ed7e5a0b824a691 100644
--- a/services/travis/travis-build.service.js
+++ b/services/travis/travis-build.service.js
@@ -1,6 +1,10 @@
 import Joi from 'joi'
 import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
-import { BaseSvgScrapingService, deprecatedService } from '../index.js'
+import {
+  BaseSvgScrapingService,
+  deprecatedService,
+  pathParams,
+} from '../index.js'
 
 const schema = Joi.object({
   message: Joi.alternatives()
@@ -17,30 +21,42 @@ export class TravisComBuild extends BaseSvgScrapingService {
     capture: ['userRepo', 'branch'],
   }
 
-  static examples = [
-    {
-      title: 'Travis (.com)',
-      pattern: 'com/:user/:repo',
-      namedParams: { user: 'ivandelabeldad', repo: 'rackian-gateway' },
-      staticPreview: {
-        message: 'passing',
-        color: 'brightgreen',
+  static openApi = {
+    '/travis/com/{user}/{repo}': {
+      get: {
+        summary: 'Travis (.com)',
+        parameters: pathParams(
+          {
+            name: 'user',
+            example: 'ivandelabeldad',
+          },
+          {
+            name: 'repo',
+            example: 'rackian-gateway',
+          },
+        ),
       },
     },
-    {
-      title: 'Travis (.com) branch',
-      pattern: 'com/:user/:repo/:branch',
-      namedParams: {
-        user: 'ivandelabeldad',
-        repo: 'rackian-gateway',
-        branch: 'master',
-      },
-      staticPreview: {
-        message: 'passing',
-        color: 'brightgreen',
+    '/travis/com/{user}/{repo}/{branch}': {
+      get: {
+        summary: 'Travis (.com) branch',
+        parameters: pathParams(
+          {
+            name: 'user',
+            example: 'ivandelabeldad',
+          },
+          {
+            name: 'repo',
+            example: 'rackian-gateway',
+          },
+          {
+            name: 'branch',
+            example: 'master',
+          },
+        ),
       },
     },
-  ]
+  }
 
   static staticPreview = {
     message: 'passing',
diff --git a/services/uptimerobot/uptimerobot-ratio.service.js b/services/uptimerobot/uptimerobot-ratio.service.js
index fa98e8ed1fd053b73fae87fdb14178d4dd0dfffe..5b1d36eaf8ed8e3e95b1ec16b02fa6ee2d1af824 100644
--- a/services/uptimerobot/uptimerobot-ratio.service.js
+++ b/services/uptimerobot/uptimerobot-ratio.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { colorScale } from '../color-formatters.js'
 import UptimeRobotBase from './uptimerobot-base.js'
 
@@ -9,24 +10,26 @@ export default class UptimeRobotRatio extends UptimeRobotBase {
     pattern: ':numberOfDays(\\d+)?/:monitorSpecificKey',
   }
 
-  static examples = [
-    {
-      title: 'Uptime Robot ratio (30 days)',
-      pattern: ':monitorSpecificKey',
-      namedParams: {
-        monitorSpecificKey: 'm778918918-3e92c097147760ee39d02d36',
+  static openApi = {
+    '/uptimerobot/ratio/{monitorSpecificKey}': {
+      get: {
+        summary: 'Uptime Robot ratio (30 days)',
+        parameters: pathParams({
+          name: 'monitorSpecificKey',
+          example: 'm778918918-3e92c097147760ee39d02d36',
+        }),
       },
-      staticPreview: this.render({ ratio: 100 }),
     },
-    {
-      title: 'Uptime Robot ratio (7 days)',
-      pattern: '7/:monitorSpecificKey',
-      namedParams: {
-        monitorSpecificKey: 'm778918918-3e92c097147760ee39d02d36',
+    '/uptimerobot/ratio/7/{monitorSpecificKey}': {
+      get: {
+        summary: 'Uptime Robot ratio (7 days)',
+        parameters: pathParams({
+          name: 'monitorSpecificKey',
+          example: 'm778918918-3e92c097147760ee39d02d36',
+        }),
       },
-      staticPreview: this.render({ ratio: 100 }),
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'uptime',
diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js
index cba29c3bcdc3aa5b8609c7e14d7d8299a6d09e1a..5c7d507b985d2bff69feb291e410eb827ff0c234 100644
--- a/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js
+++ b/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js
@@ -1,7 +1,8 @@
+import { pathParams } from '../index.js'
 import { renderDownloadsBadge } from '../downloads.js'
 import VisualStudioMarketplaceBase from './visual-studio-marketplace-base.js'
 
-const documentation = `
+const description = `
 <p>
   This is for Visual Studio and Visual Studio Code Extensions.
 </p>
@@ -19,24 +20,28 @@ export default class VisualStudioMarketplaceDownloads extends VisualStudioMarket
       '(visual-studio-marketplace|vscode-marketplace)/:measure(d|i)/:extensionId',
   }
 
-  static examples = [
-    {
-      title: 'Visual Studio Marketplace Installs',
-      pattern: 'visual-studio-marketplace/i/:extensionId',
-      namedParams: { extensionId: 'ritwickdey.LiveServer' },
-      staticPreview: this.render({ measure: 'i', downloads: 843 }),
-      keywords: this.keywords,
-      documentation,
+  static openApi = {
+    '/visual-studio-marketplace/i/{extensionId}': {
+      get: {
+        summary: 'Visual Studio Marketplace Installs',
+        description,
+        parameters: pathParams({
+          name: 'extensionId',
+          example: 'ritwickdey.LiveServer',
+        }),
+      },
     },
-    {
-      title: 'Visual Studio Marketplace Downloads',
-      pattern: 'visual-studio-marketplace/d/:extensionId',
-      namedParams: { extensionId: 'ritwickdey.LiveServer' },
-      staticPreview: this.render({ measure: 'd', downloads: 1239 }),
-      keywords: this.keywords,
-      documentation,
+    '/visual-studio-marketplace/d/{extensionId}': {
+      get: {
+        summary: 'Visual Studio Marketplace Downloads',
+        description,
+        parameters: pathParams({
+          name: 'extensionId',
+          example: 'ritwickdey.LiveServer',
+        }),
+      },
     },
-  ]
+  }
 
   static render({ measure, downloads }) {
     const labelOverride = measure === 'd' ? 'downloads' : 'installs'
diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js
index 0c3cb2eda2ccf5b04e9282dc618b6a3655361a3e..01352ee3cf82aea0b24a1f3ab05860de09049686 100644
--- a/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js
+++ b/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { starRating } from '../text-formatters.js'
 import { floorCount } from '../color-formatters.js'
 import VisualStudioMarketplaceBase from './visual-studio-marketplace-base.js'
@@ -11,29 +12,25 @@ export default class VisualStudioMarketplaceRating extends VisualStudioMarketpla
       '(visual-studio-marketplace|vscode-marketplace)/:format(r|stars)/:extensionId',
   }
 
-  static examples = [
-    {
-      title: 'Visual Studio Marketplace Rating',
-      pattern: 'visual-studio-marketplace/r/:extensionId',
-      namedParams: { extensionId: 'ritwickdey.LiveServer' },
-      staticPreview: this.render({
-        format: 'r',
-        averageRating: 4.79,
-        ratingCount: 145,
-      }),
-      keywords: this.keywords,
+  static openApi = {
+    '/visual-studio-marketplace/{format}/{extensionId}': {
+      get: {
+        summary: 'Visual Studio Marketplace Rating',
+        parameters: pathParams(
+          {
+            name: 'format',
+            example: 'r',
+            description: 'rating (r) or stars',
+            schema: { type: 'string', enum: this.getEnum('format') },
+          },
+          {
+            name: 'extensionId',
+            example: 'ritwickdey.LiveServer',
+          },
+        ),
+      },
     },
-    {
-      title: 'Visual Studio Marketplace Rating (Stars)',
-      pattern: 'visual-studio-marketplace/stars/:extensionId',
-      namedParams: { extensionId: 'ritwickdey.LiveServer' },
-      staticPreview: this.render({
-        format: 'stars',
-        averageRating: 4.5,
-      }),
-      keywords: this.keywords,
-    },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'rating',