diff --git a/core/base-service/base.js b/core/base-service/base.js
index 96efddbf920e2b31a5541290bf9bf0f12bd331c8..4f6d3da09e746ff737767ec1087b5ac8acc76f45 100644
--- a/core/base-service/base.js
+++ b/core/base-service/base.js
@@ -21,6 +21,7 @@ import {
 } from './errors.js'
 import { validateExample, transformExample } from './examples.js'
 import { fetch } from './got.js'
+import { getEnum } from './openapi.js'
 import {
   makeFullUrl,
   assertValidRoute,
@@ -102,6 +103,26 @@ class BaseService {
     throw new Error(`Route not defined for ${this.name}`)
   }
 
+  /**
+   * Extract an array of allowed values from this service's route pattern
+   * for a given route parameter
+   *
+   * @param {string} param The name of a param in this service's route pattern
+   * @returns {string[]} Array of allowed values for this param
+   */
+  static getEnum(param) {
+    if (!('pattern' in this.route)) {
+      throw new Error('getEnum() requires route to have a .pattern property')
+    }
+    const enumeration = getEnum(this.route.pattern, param)
+    if (!Array.isArray(enumeration)) {
+      throw new Error(
+        `Could not extract enum for param ${param} from pattern ${this.route.pattern}`,
+      )
+    }
+    return enumeration
+  }
+
   /**
    * Configuration for the authentication helper that prepares credentials
    * for upstream requests.
diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js
index 8f63654679bec8c00a3b7f5be45d28e650a929b8..dbd096e600bc255553cebd7988dcf525ba944cb5 100644
--- a/core/base-service/base.spec.js
+++ b/core/base-service/base.spec.js
@@ -539,6 +539,7 @@ describe('BaseService', function () {
       ).to.not.contain('service_response_bytes_bucket')
     })
   })
+
   describe('auth', function () {
     class AuthService extends DummyService {
       static auth = {
@@ -592,4 +593,44 @@ describe('BaseService', function () {
       })
     })
   })
+
+  describe('getEnum', function () {
+    class EnumService extends DummyService {
+      static route = {
+        base: 'foo',
+        pattern: ':namedParamA/:namedParamB(this|that)',
+        queryParamSchema,
+      }
+    }
+
+    it('returns an array of allowed values', async function () {
+      expect(EnumService.getEnum('namedParamB')).to.deep.equal(['this', 'that'])
+    })
+
+    it('throws if param name is invalid', async function () {
+      expect(() => EnumService.getEnum('notAValidParam')).to.throw(
+        'Could not extract enum for param notAValidParam from pattern :namedParamA/:namedParamB(this|that)',
+      )
+    })
+
+    it('throws if param name is not an enum', async function () {
+      expect(() => EnumService.getEnum('namedParamA')).to.throw(
+        'Could not extract enum for param namedParamA from pattern :namedParamA/:namedParamB(this|that)',
+      )
+    })
+
+    it('throws if route does not have a pattern', async function () {
+      class FormatService extends DummyService {
+        static route = {
+          base: 'foo',
+          format: '([^/]+?)',
+          queryParamSchema,
+        }
+      }
+
+      expect(() => FormatService.getEnum('notAValidParam')).to.throw(
+        'getEnum() requires route to have a .pattern property',
+      )
+    })
+  })
 })
diff --git a/core/base-service/openapi.js b/core/base-service/openapi.js
index 92847b71f52ae6d20caad7410d598686877afd9d..a5399a328cdce01011f7ae9ac17f32ab10669bf2 100644
--- a/core/base-service/openapi.js
+++ b/core/base-service/openapi.js
@@ -466,4 +466,11 @@ function queryParams(...params) {
  * @property {boolean} allowEmptyValue If true, allows the ability to pass an empty value to this parameter
  */
 
-export { category2openapi, pathParam, pathParams, queryParam, queryParams }
+export {
+  category2openapi,
+  getEnum,
+  pathParam,
+  pathParams,
+  queryParam,
+  queryParams,
+}
diff --git a/services/codefactor/codefactor-grade.service.js b/services/codefactor/codefactor-grade.service.js
index 0752654565fcac5c7fa9fd86f4b7ee4375113b2d..26ed28208b7bc140e84ace59521d5e996b0fc9d3 100644
--- a/services/codefactor/codefactor-grade.service.js
+++ b/services/codefactor/codefactor-grade.service.js
@@ -1,5 +1,5 @@
 import Joi from 'joi'
-import { BaseSvgScrapingService } from '../index.js'
+import { BaseSvgScrapingService, pathParams } from '../index.js'
 import { isValidGrade, gradeColor } from './codefactor-helpers.js'
 
 const schema = Joi.object({
@@ -13,18 +13,52 @@ export default class CodeFactorGrade extends BaseSvgScrapingService {
     pattern: ':vcsType(github|bitbucket)/:user/:repo/:branch*',
   }
 
-  static examples = [
-    {
-      title: 'CodeFactor Grade',
-      namedParams: {
-        vcsType: 'github',
-        user: 'microsoft',
-        repo: 'powertoys',
-        branch: 'main',
+  static openApi = {
+    '/codefactor/grade/{vcsType}/{user}/{repo}/{branch}': {
+      get: {
+        summary: 'CodeFactor Grade (with branch)',
+        parameters: pathParams(
+          {
+            name: 'vcsType',
+            example: 'github',
+            schema: { type: 'string', enum: this.getEnum('vcsType') },
+          },
+          {
+            name: 'user',
+            example: 'microsoft',
+          },
+          {
+            name: 'repo',
+            example: 'powertoys',
+          },
+          {
+            name: 'branch',
+            example: 'main',
+          },
+        ),
       },
-      staticPreview: this.render({ grade: 'B+' }),
     },
-  ]
+    '/codefactor/grade/{vcsType}/{user}/{repo}': {
+      get: {
+        summary: 'CodeFactor Grade',
+        parameters: pathParams(
+          {
+            name: 'vcsType',
+            example: 'github',
+            schema: { type: 'string', enum: this.getEnum('vcsType') },
+          },
+          {
+            name: 'user',
+            example: 'microsoft',
+          },
+          {
+            name: 'repo',
+            example: 'powertoys',
+          },
+        ),
+      },
+    },
+  }
 
   static defaultBadgeData = { label: 'code quality' }
 
diff --git a/services/conda/conda-downloads.service.js b/services/conda/conda-downloads.service.js
index a08152ed42729cd0c52e0797ed4362ba18ee79e0..85b6e6b897aea08bc19f9274d685de2d0181896d 100644
--- a/services/conda/conda-downloads.service.js
+++ b/services/conda/conda-downloads.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { renderDownloadsBadge } from '../downloads.js'
 import BaseCondaService from './conda-base.js'
 
@@ -5,14 +6,28 @@ export default class CondaDownloads extends BaseCondaService {
   static category = 'downloads'
   static route = { base: 'conda', pattern: ':variant(d|dn)/:channel/:pkg' }
 
-  static examples = [
-    {
-      title: 'Conda',
-      namedParams: { channel: 'conda-forge', package: 'python' },
-      pattern: 'dn/:channel/:package',
-      staticPreview: this.render({ variant: 'dn', downloads: 5000000 }),
+  static openApi = {
+    '/conda/{variant}/{channel}/{package}': {
+      get: {
+        summary: 'Conda Downloads',
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'dn',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'channel',
+            example: 'conda-forge',
+          },
+          {
+            name: 'package',
+            example: 'python',
+          },
+        ),
+      },
     },
-  ]
+  }
 
   static render({ variant, downloads }) {
     const labelOverride = variant === 'dn' ? 'downloads' : 'conda|downloads'
diff --git a/services/conda/conda-platform.service.js b/services/conda/conda-platform.service.js
index fdf7cc13aa322cc9b6d9dc54fca9540232c05f9c..e16a8399a4be3b89a3869a20a6518ad7d4815535 100644
--- a/services/conda/conda-platform.service.js
+++ b/services/conda/conda-platform.service.js
@@ -1,20 +1,32 @@
+import { pathParams } from '../index.js'
 import BaseCondaService from './conda-base.js'
 
 export default class CondaPlatform extends BaseCondaService {
   static category = 'platform-support'
   static route = { base: 'conda', pattern: ':variant(p|pn)/:channel/:pkg' }
 
-  static examples = [
-    {
-      title: 'Conda',
-      namedParams: { channel: 'conda-forge', package: 'python' },
-      pattern: 'pn/:channel/:package',
-      staticPreview: this.render({
-        variant: 'pn',
-        platforms: ['linux-64', 'win-32', 'osx-64', 'win-64'],
-      }),
+  static openApi = {
+    '/conda/{variant}/{channel}/{package}': {
+      get: {
+        summary: 'Conda Platform',
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'pn',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'channel',
+            example: 'conda-forge',
+          },
+          {
+            name: 'package',
+            example: 'python',
+          },
+        ),
+      },
     },
-  ]
+  }
 
   static render({ variant, platforms }) {
     return {
diff --git a/services/depfu/depfu.service.js b/services/depfu/depfu.service.js
index fd26a9e4297db719cb4e122ffb087d70a6342970..a1c0dbc2eec938b9b3585024fe642a701123cf6c 100644
--- a/services/depfu/depfu.service.js
+++ b/services/depfu/depfu.service.js
@@ -1,5 +1,10 @@
 import Joi from 'joi'
-import { BaseJsonService, InvalidParameter, redirector } from '../index.js'
+import {
+  BaseJsonService,
+  InvalidParameter,
+  redirector,
+  pathParams,
+} from '../index.js'
 
 const depfuSchema = Joi.object({
   text: Joi.string().required(),
@@ -13,16 +18,24 @@ class Depfu extends BaseJsonService {
     pattern: ':vcsType(github|gitlab)/:project+',
   }
 
-  static examples = [
-    {
-      title: 'Depfu',
-      namedParams: { vcsType: 'github', project: 'depfu/example-ruby' },
-      staticPreview: this.render({
-        text: 'recent',
-        colorscheme: 'brightgreen',
-      }),
+  static openApi = {
+    '/depfu/dependencies/{vcsType}/{project}': {
+      get: {
+        summary: 'Depfu',
+        parameters: pathParams(
+          {
+            name: 'vcsType',
+            example: 'github',
+            schema: { type: 'string', enum: this.getEnum('vcsType') },
+          },
+          {
+            name: 'project',
+            example: 'depfu/example-ruby',
+          },
+        ),
+      },
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'dependencies' }
 
diff --git a/services/github/github-contributors.service.js b/services/github/github-contributors.service.js
index 72162af27a79fe48c86d39e8ae58560b40c369e9..23d07dfe48c773b77b3457238ba5bbaf3e06eae8 100644
--- a/services/github/github-contributors.service.js
+++ b/services/github/github-contributors.service.js
@@ -1,5 +1,6 @@
 import Joi from 'joi'
 import parseLinkHeader from 'parse-link-header'
+import { pathParams } from '../index.js'
 import { renderContributorBadge } from '../contributor-count.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import { documentation, httpErrorsFor } from './github-helpers.js'
@@ -14,18 +15,29 @@ export default class GithubContributors extends GithubAuthV3Service {
     pattern: ':variant(contributors|contributors-anon)/:user/:repo',
   }
 
-  static examples = [
-    {
-      title: 'GitHub contributors',
-      namedParams: {
-        variant: 'contributors',
-        user: 'cdnjs',
-        repo: 'cdnjs',
+  static openApi = {
+    '/github/{variant}/{user}/{repo}': {
+      get: {
+        summary: 'GitHub contributors',
+        description: documentation,
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'contributors',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'user',
+            example: 'cdnjs',
+          },
+          {
+            name: 'repo',
+            example: 'cdnjs',
+          },
+        ),
       },
-      staticPreview: this.render({ contributorCount: 397 }),
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'contributors' }
 
diff --git a/services/github/github-issue-detail.service.js b/services/github/github-issue-detail.service.js
index c391c15bb86e32279414c3bd69c691ce5fdb8b40..f6fb5039ef4b078a303429316137c4a8041c7e4f 100644
--- a/services/github/github-issue-detail.service.js
+++ b/services/github/github-issue-detail.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { nonNegativeInteger } from '../validators.js'
 import { formatDate, metric } from '../text-formatters.js'
 import { age } from '../color-formatters.js'
-import { InvalidResponse } from '../index.js'
+import { InvalidResponse, pathParams } from '../index.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import {
   documentation,
@@ -179,35 +179,38 @@ export default class GithubIssueDetail extends GithubAuthV3Service {
       ':issueKind(issues|pulls)/detail/:property(state|title|author|label|comments|age|last-update|milestone)/:user/:repo/:number([0-9]+)',
   }
 
-  static examples = [
-    {
-      title: 'GitHub issue/pull request detail',
-      namedParams: {
-        issueKind: 'issues',
-        property: 'state',
-        user: 'badges',
-        repo: 'shields',
-        number: '979',
+  static openApi = {
+    '/github/{issueKind}/detail/{property}/{user}/{repo}/{number}': {
+      get: {
+        summary: 'GitHub issue/pull request detail',
+        description: documentation,
+        parameters: pathParams(
+          {
+            name: 'issueKind',
+            example: 'issues',
+            schema: { type: 'string', enum: this.getEnum('issueKind') },
+          },
+          {
+            name: 'property',
+            example: 'state',
+            schema: { type: 'string', enum: this.getEnum('property') },
+          },
+          {
+            name: 'user',
+            example: 'badges',
+          },
+          {
+            name: 'repo',
+            example: 'shields',
+          },
+          {
+            name: 'number',
+            example: '979',
+          },
+        ),
       },
-      staticPreview: this.render({
-        property: 'state',
-        value: { state: 'closed' },
-        isPR: false,
-        number: '979',
-      }),
-      keywords: [
-        'state',
-        'title',
-        'author',
-        'label',
-        'comments',
-        'age',
-        'last update',
-        'milestone',
-      ],
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'issue/pull request',
diff --git a/services/github/github-milestone-detail.service.js b/services/github/github-milestone-detail.service.js
index fe37ffb1f89fc41570a2c89c75352505879f8c3d..98be914dadfb02a86d7152de0a22dd2aeda8c10f 100644
--- a/services/github/github-milestone-detail.service.js
+++ b/services/github/github-milestone-detail.service.js
@@ -1,4 +1,5 @@
 import Joi from 'joi'
+import { pathParams } from '../index.js'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
@@ -18,23 +19,33 @@ export default class GithubMilestoneDetail extends GithubAuthV3Service {
       ':variant(issues-closed|issues-open|issues-total|progress|progress-percent)/:user/:repo/:number([0-9]+)',
   }
 
-  static examples = [
-    {
-      title: 'GitHub milestone',
-      namedParams: {
-        variant: 'issues-open',
-        user: 'badges',
-        repo: 'shields',
-        number: '1',
+  static openApi = {
+    '/github/milestones/{variant}/{user}/{repo}/{number}': {
+      get: {
+        summary: 'GitHub milestone details',
+        description: documentation,
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'issues-open',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'user',
+            example: 'badges',
+          },
+          {
+            name: 'repo',
+            example: 'shields',
+          },
+          {
+            name: 'number',
+            example: '1',
+          },
+        ),
       },
-      staticPreview: {
-        label: 'milestone issues',
-        message: '17/22',
-        color: 'blue',
-      },
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'milestones', color: 'informational' }
 
diff --git a/services/github/github-milestone.service.js b/services/github/github-milestone.service.js
index 973bb6649eae493016e2613900de3a646b8ba461..458071dcf023adcd72d0eef923ae0c96f7eef4f4 100644
--- a/services/github/github-milestone.service.js
+++ b/services/github/github-milestone.service.js
@@ -1,4 +1,5 @@
 import Joi from 'joi'
+import { pathParams } from '../index.js'
 import { metric } from '../text-formatters.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import { documentation, httpErrorsFor } from './github-helpers.js'
@@ -18,22 +19,29 @@ export default class GithubMilestone extends GithubAuthV3Service {
     pattern: ':variant(open|closed|all)/:user/:repo',
   }
 
-  static examples = [
-    {
-      title: 'GitHub milestones',
-      namedParams: {
-        user: 'badges',
-        repo: 'shields',
-        variant: 'open',
+  static openApi = {
+    '/github/milestones/{variant}/{user}/{repo}': {
+      get: {
+        summary: 'GitHub number of milestones',
+        description: documentation,
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'open',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'user',
+            example: 'badges',
+          },
+          {
+            name: 'repo',
+            example: 'shields',
+          },
+        ),
       },
-      staticPreview: {
-        label: 'milestones',
-        message: '2',
-        color: 'red',
-      },
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'milestones',
diff --git a/services/homebrew/homebrew-downloads.service.js b/services/homebrew/homebrew-downloads.service.js
index e7989e48b362a4122969441f66f6a14f8bdff620..6dfa10d1a3d4ae1e78e993979a3e1cb6918e8357 100644
--- a/services/homebrew/homebrew-downloads.service.js
+++ b/services/homebrew/homebrew-downloads.service.js
@@ -1,6 +1,6 @@
 import Joi from 'joi'
 import { renderDownloadsBadge } from '../downloads.js'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, pathParams } from '../index.js'
 import { nonNegativeInteger } from '../validators.js'
 
 function getSchema({ formula }) {
@@ -38,13 +38,24 @@ export default class HomebrewDownloads extends BaseJsonService {
     pattern: 'installs/:interval(dm|dq|dy)/:formula',
   }
 
-  static examples = [
-    {
-      title: 'homebrew downloads',
-      namedParams: { interval: 'dm', formula: 'cake' },
-      staticPreview: renderDownloadsBadge({ interval: 'month', downloads: 93 }),
+  static openApi = {
+    '/homebrew/installs/{interval}/{formula}': {
+      get: {
+        summary: 'homebrew downloads',
+        parameters: pathParams(
+          {
+            name: 'interval',
+            example: 'dm',
+            schema: { type: 'string', enum: this.getEnum('interval') },
+          },
+          {
+            name: 'formula',
+            example: 'cake',
+          },
+        ),
+      },
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'downloads' }
 
diff --git a/services/jsdelivr/jsdelivr-hits-github.service.js b/services/jsdelivr/jsdelivr-hits-github.service.js
index 8d760b899266f3b4cf34b9d668f7dfe30619be9e..96ddc830f396f637fdc0ba519b0db0196c42ba91 100644
--- a/services/jsdelivr/jsdelivr-hits-github.service.js
+++ b/services/jsdelivr/jsdelivr-hits-github.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { schema, periodMap, BaseJsDelivrService } from './jsdelivr-base.js'
 
 export default class JsDelivrHitsGitHub extends BaseJsDelivrService {
@@ -6,17 +7,28 @@ export default class JsDelivrHitsGitHub extends BaseJsDelivrService {
     pattern: ':period(hd|hw|hm|hy)/:user/:repo',
   }
 
-  static examples = [
-    {
-      title: 'jsDelivr hits (GitHub)',
-      namedParams: {
-        period: 'hm',
-        user: 'jquery',
-        repo: 'jquery',
+  static openApi = {
+    '/jsdelivr/gh/{period}/{user}/{repo}': {
+      get: {
+        summary: 'jsDelivr hits (GitHub)',
+        parameters: pathParams(
+          {
+            name: 'period',
+            example: 'hm',
+            schema: { type: 'string', enum: this.getEnum('period') },
+          },
+          {
+            name: 'user',
+            example: 'jquery',
+          },
+          {
+            name: 'repo',
+            example: 'jquery',
+          },
+        ),
       },
-      staticPreview: this.render({ period: 'hm', hits: 9809876 }),
     },
-  ]
+  }
 
   async fetch({ period, user, repo }) {
     return this._requestJson({
diff --git a/services/reddit/user-karma.service.js b/services/reddit/user-karma.service.js
index 0ec007edc30badfeddf1f1500d70b664c51d0364..ca8b489d3451b076b6eeabbdd0d50cc78e447695 100644
--- a/services/reddit/user-karma.service.js
+++ b/services/reddit/user-karma.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { anyInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, pathParams } from '../index.js'
 
 const schema = Joi.object({
   data: Joi.object({
@@ -18,18 +18,24 @@ export default class RedditUserKarma extends BaseJsonService {
     pattern: ':variant(link|comment|combined)/:user',
   }
 
-  static examples = [
-    {
-      title: 'Reddit User Karma',
-      namedParams: { variant: 'combined', user: 'example' },
-      staticPreview: {
-        label: 'combined karma',
-        message: 56,
-        color: 'brightgreen',
-        style: 'social',
+  static openApi = {
+    '/reddit/user-karma/{variant}/{user}': {
+      get: {
+        summary: 'Reddit User Karma',
+        parameters: pathParams(
+          {
+            name: 'variant',
+            example: 'combined',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          },
+          {
+            name: 'user',
+            example: 'example',
+          },
+        ),
       },
     },
-  ]
+  }
 
   static _cacheLength = 7200
 
diff --git a/services/sourceforge/sourceforge-open-tickets.service.js b/services/sourceforge/sourceforge-open-tickets.service.js
index d6fa6b2c26a2eb4899e69bab7511c80ef6ae8ea7..7c1cf76ae31a5ee65b8c5a5f114cfb955c4af362 100644
--- a/services/sourceforge/sourceforge-open-tickets.service.js
+++ b/services/sourceforge/sourceforge-open-tickets.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
-import { BaseJsonService } from '../index.js'
+import { BaseJsonService, pathParams } from '../index.js'
 
 const schema = Joi.object({
   count: nonNegativeInteger.required(),
@@ -15,16 +15,24 @@ export default class SourceforgeOpenTickets extends BaseJsonService {
     pattern: ':project/:type(bugs|feature-requests)',
   }
 
-  static examples = [
-    {
-      title: 'Sourceforge Open Tickets',
-      namedParams: {
-        type: 'bugs',
-        project: 'sevenzip',
+  static openApi = {
+    '/sourceforge/open-tickets/{project}/{type}': {
+      get: {
+        summary: 'Sourceforge Open Tickets',
+        parameters: pathParams(
+          {
+            name: 'project',
+            example: 'sevenzip',
+          },
+          {
+            name: 'type',
+            example: 'bugs',
+            schema: { type: 'string', enum: this.getEnum('type') },
+          },
+        ),
       },
-      staticPreview: this.render({ count: 1338 }),
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'open tickets',
diff --git a/services/testspace/testspace-test-count.service.js b/services/testspace/testspace-test-count.service.js
index 09ec0e68142608995f32ab894482af140841e38a..aa670fc3be470671a6f3e311ded2ad08acb400b9 100644
--- a/services/testspace/testspace-test-count.service.js
+++ b/services/testspace/testspace-test-count.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { metric as metricCount } from '../text-formatters.js'
 import TestspaceBase from './testspace-base.js'
 
@@ -8,21 +9,32 @@ export default class TestspaceTestCount extends TestspaceBase {
       ':metric(total|passed|failed|skipped|errored|untested)/:org/:project/:space+',
   }
 
-  static examples = [
-    {
-      title: 'Testspace tests',
-      namedParams: {
-        metric: 'passed',
-        org: 'swellaby',
-        project: 'swellaby:testspace-sample',
-        space: 'main',
+  static openApi = {
+    '/testspace/{metric}/{org}/{project}/{space}': {
+      get: {
+        summary: 'Testspace tests',
+        parameters: pathParams(
+          {
+            name: 'metric',
+            example: 'passed',
+            schema: { type: 'string', enum: this.getEnum('metric') },
+          },
+          {
+            name: 'org',
+            example: 'swellaby',
+          },
+          {
+            name: 'project',
+            example: 'swellaby:testspace-sample',
+          },
+          {
+            name: 'space',
+            example: 'main',
+          },
+        ),
       },
-      staticPreview: this.render({
-        metric: 'passed',
-        value: 31,
-      }),
     },
-  ]
+  }
 
   static render({ value, metric }) {
     let color = 'informational'
diff --git a/services/vaadin-directory/vaadin-directory-rating.service.js b/services/vaadin-directory/vaadin-directory-rating.service.js
index c07ddf091b09933ce1b029f6e3ed78b1f508eb87..846e7416558bd4273eb5c2058a300e90c840ef1a 100644
--- a/services/vaadin-directory/vaadin-directory-rating.service.js
+++ b/services/vaadin-directory/vaadin-directory-rating.service.js
@@ -1,3 +1,4 @@
+import { pathParams } from '../index.js'
 import { starRating } from '../text-formatters.js'
 import { floorCount as floorCountColor } from '../color-formatters.js'
 import { BaseVaadinDirectoryService } from './vaadin-directory-base.js'
@@ -10,15 +11,24 @@ export default class VaadinDirectoryRating extends BaseVaadinDirectoryService {
     pattern: ':format(star|stars|rating)/:packageName',
   }
 
-  static examples = [
-    {
-      title: 'Vaadin Directory',
-      pattern: ':format(stars|rating)/:packageName',
-      namedParams: { format: 'rating', packageName: 'vaadinvaadin-grid' },
-      staticPreview: this.render({ format: 'rating', score: 4.75 }),
-      keywords: ['vaadin-directory'],
+  static openApi = {
+    '/vaadin-directory/{format}/{packageName}': {
+      get: {
+        summary: 'Vaadin Directory Rating',
+        parameters: pathParams(
+          {
+            name: 'format',
+            example: 'rating',
+            schema: { type: 'string', enum: this.getEnum('format') },
+          },
+          {
+            name: 'packageName',
+            example: 'vaadinvaadin-grid',
+          },
+        ),
+      },
     },
-  ]
+  }
 
   static defaultBadgeData = {
     label: 'rating',