From 14892e3943a4677332618d8b9f584766f7940ee7 Mon Sep 17 00:00:00 2001
From: chris48s <chris48s@users.noreply.github.com>
Date: Tue, 13 Jun 2023 21:08:43 +0100
Subject: [PATCH] Implement a pattern for dealing with upstream APIs which are
 slow on the first hit; affects [endpoint] (#9233)

* allow serviceData to override cacheSeconds with a longer value

* prevent [endpoint] json cacheSeconds property exceeding service default

* allow ShieldsRuntimeError to specify a cacheSeconds property

By default error responses use the cacheLength of
the service class throwing the error.

This allows error to tell the handling layer the maxAge
that should be set on the error badge response.

* add customExceptions param

This

1. allows us to specify custom properties to pass to the exception
   constructor if we throw any of the standard got errors
   e.g: `ETIMEDOUT`, `ECONNRESET`, etc
2. uses a custom `cacheSeconds` property (if set on the exception)
   to set the response maxAge

* customExceptions --> systemErrors

* errorMessages --> httpErrors
---
 core/base-service/base-graphql.js             | 10 ++++++-
 core/base-service/base-json.js                | 19 ++++++++++--
 core/base-service/base-svg-scraping.js        | 14 +++++++--
 core/base-service/base-xml.js                 | 14 +++++++--
 core/base-service/base-yaml.js                | 14 +++++++--
 core/base-service/base.js                     | 16 +++++++---
 core/base-service/cache-headers.js            |  2 +-
 core/base-service/cache-headers.spec.js       |  4 +--
 core/base-service/check-error-response.js     | 10 +++----
 core/base-service/errors.js                   |  4 +++
 core/base-service/got.js                      |  8 ++++-
 core/base-service/got.spec.js                 | 30 +++++++++++++++++++
 core/base-service/legacy-request-handler.js   |  8 ++---
 .../legacy-request-handler.spec.js            |  6 ++--
 doc/TUTORIAL.md                               |  4 +--
 doc/service-tests.md                          |  2 +-
 services/appveyor/appveyor-base.js            |  2 +-
 services/azure-devops/azure-devops-base.js    |  8 ++---
 .../azure-devops-build.service.js             |  2 +-
 .../azure-devops-coverage.service.js          |  6 ++--
 services/azure-devops/azure-devops-helpers.js |  7 ++---
 .../azure-devops-release.service.js           |  2 +-
 .../azure-devops-tests.service.js             |  6 ++--
 services/bit/bit-components.service.js        |  2 +-
 .../bitbucket/bitbucket-issues.service.js     |  2 +-
 .../bitbucket/bitbucket-pipelines.service.js  |  2 +-
 .../bitbucket-pull-request.service.js         |  6 ++--
 services/bitrise/bitrise.service.js           |  2 +-
 services/bower/bower-base.js                  |  2 +-
 services/bundlephobia/bundlephobia.service.js |  2 +-
 .../cii-best-practices.service.js             |  2 +-
 services/circleci/circleci.service.js         |  2 +-
 .../clearlydefined-score.service.js           |  2 +-
 services/codacy/codacy-coverage.service.js    |  2 +-
 services/codacy/codacy-grade.service.js       |  2 +-
 services/codecov/codecov.service.js           |  4 +--
 .../codefactor/codefactor-grade.service.js    |  2 +-
 services/coincap/coincap-base.js              |  2 +-
 services/coveralls/coveralls.service.js       |  2 +-
 services/coverity/coverity-scan.service.js    |  2 +-
 services/discord/discord.service.js           |  2 +-
 services/docker/docker-automated.service.js   |  2 +-
 services/docker/docker-cloud-common-fetch.js  |  2 +-
 services/docker/docker-pulls.service.js       |  2 +-
 services/docker/docker-size.service.js        |  2 +-
 services/docker/docker-stars.service.js       |  2 +-
 services/docker/docker-version.service.js     |  2 +-
 services/drone/drone-build.service.js         |  2 +-
 services/dynamic-common.js                    |  4 +--
 services/dynamic/dynamic-json.service.js      |  4 +--
 services/dynamic/dynamic-xml.service.js       |  4 +--
 services/dynamic/dynamic-yaml.service.js      |  4 +--
 services/dynamic/json-path.js                 |  8 ++---
 .../eclipse-marketplace-base.js               |  2 +-
 services/ecologi/ecologi-carbon.service.js    |  2 +-
 services/ecologi/ecologi-trees.service.js     |  2 +-
 services/elm-package/elm-package.service.js   |  2 +-
 services/endpoint-common.js                   |  6 ++--
 services/endpoint/endpoint.service.js         |  9 ++++--
 services/f-droid/f-droid.service.js           |  2 +-
 .../factorio-mod-portal.service.js            |  2 +-
 services/fedora/fedora.service.js             |  2 +-
 services/feedz/feedz.service.js               |  4 +--
 services/gem/gem-downloads.service.js         |  4 +--
 services/gerrit/gerrit.service.js             |  2 +-
 .../gist/github-gist-last-commit.service.js   |  4 +--
 .../github-actions-workflow-status.service.js |  2 +-
 .../github/github-checks-status.service.js    |  4 +--
 .../github/github-commit-status.service.js    |  4 +--
 .../github-commits-difference.service.js      |  4 +--
 .../github/github-commits-since.service.js    |  4 +--
 services/github/github-common-fetch.js        | 12 ++++----
 services/github/github-common-release.js      |  6 ++--
 .../github/github-contributors.service.js     |  4 +--
 services/github/github-downloads.service.js   |  6 ++--
 services/github/github-followers.service.js   |  4 +--
 services/github/github-helpers.js             |  4 +--
 .../github/github-issue-detail.service.js     |  4 +--
 services/github/github-labels.service.js      |  4 +--
 services/github/github-languages-base.js      |  4 +--
 services/github/github-last-commit.service.js |  4 +--
 services/github/github-license.service.js     |  4 +--
 .../github/github-milestone-detail.service.js |  4 +--
 services/github/github-milestone.service.js   |  4 +--
 ...github-pull-request-check-state.service.js |  4 +--
 .../github/github-release-date.service.js     |  4 +--
 services/github/github-repo-size.service.js   |  4 +--
 services/github/github-search.service.js      |  4 +--
 services/github/github-size.service.js        |  6 ++--
 services/github/github-stars.service.js       |  4 +--
 services/github/github-watchers.service.js    |  4 +--
 services/gitlab/gitlab-base.js                |  8 ++---
 .../gitlab/gitlab-contributors.service.js     |  4 +--
 services/gitlab/gitlab-forks.service.js       |  2 +-
 services/gitlab/gitlab-helper.js              |  4 +--
 services/gitlab/gitlab-issues.service.js      |  4 +--
 .../gitlab/gitlab-languages-count.service.js  |  4 +--
 services/gitlab/gitlab-last-commit.service.js |  4 +--
 services/gitlab/gitlab-license.service.js     |  4 +--
 .../gitlab/gitlab-merge-requests.service.js   |  4 +--
 .../gitlab-pipeline-coverage.service.js       |  6 ++--
 .../gitlab/gitlab-pipeline-status.service.js  |  4 +--
 services/gitlab/gitlab-release.service.js     |  4 +--
 services/gitlab/gitlab-stars.service.js       |  2 +-
 services/gitlab/gitlab-tag.service.js         |  4 +--
 .../hackernews-user-karma.service.js          |  2 +-
 .../homebrew/homebrew-downloads.service.js    |  2 +-
 services/jenkins/jenkins-base.js              |  4 +--
 services/jenkins/jenkins-coverage.service.js  |  2 +-
 .../jenkins-plugin-installs.service.js        |  2 +-
 services/jetbrains/jetbrains-base.js          |  4 +--
 .../jetbrains/jetbrains-downloads.service.js  |  2 +-
 .../jetbrains/jetbrains-rating.service.js     |  2 +-
 .../jetbrains/jetbrains-version.service.js    |  2 +-
 services/jira/jira-issue.service.js           |  2 +-
 services/jira/jira-sprint.service.js          |  2 +-
 services/jitpack/jitpack-version.service.js   |  2 +-
 services/librariesio/librariesio-base.js      |  2 +-
 .../librariesio-dependencies.service.js       |  4 +--
 services/localizely/localizely.service.js     |  2 +-
 services/luarocks/luarocks.service.js         |  2 +-
 services/matrix/matrix.service.js             |  6 ++--
 services/nexus/nexus.service.js               |  4 +--
 services/npm/npm-base.js                      |  2 +-
 services/npm/npm-downloads.service.js         |  2 +-
 services/npm/npm-version.service.js           |  2 +-
 services/npms-io/npms-io-score.service.js     |  2 +-
 services/open-vsx/open-vsx-base.js            |  2 +-
 .../opencollective/opencollective-base.js     |  4 +--
 services/opm/opm-version.service.js           |  2 +-
 .../ossf-scorecard/ossf-scorecard.service.js  |  2 +-
 services/piwheels/piwheels-version.service.js |  2 +-
 services/pypi/pypi-base.js                    |  2 +-
 services/pypi/pypi-downloads.service.js       |  2 +-
 .../reddit/subreddit-subscribers.service.js   |  2 +-
 services/reddit/user-karma.service.js         |  2 +-
 services/reuse/reuse-compliance.service.js    |  2 +-
 services/scrutinizer/scrutinizer-base.js      |  2 +-
 services/snyk/snyk-vulnerability-base.js      |  4 +--
 .../snyk/snyk-vulnerability-github.service.js |  2 +-
 .../snyk/snyk-vulnerability-npm.service.js    |  2 +-
 services/sonar/sonar-base.js                  |  2 +-
 services/sourceforge/sourceforge-base.js      |  2 +-
 .../sourceforge-commit-count.service.js       |  2 +-
 .../sourceforge-downloads.service.js          |  2 +-
 .../sourceforge-last-commit.service.js        |  2 +-
 .../sourceforge-open-tickets.service.js       |  2 +-
 services/spack/spack.service.js               |  2 +-
 .../stackexchange-reputation.service.js       |  2 +-
 services/steam/steam-base.js                  |  2 +-
 services/symfony/symfony-insight-base.js      |  2 +-
 services/tas/tas-tests.service.js             |  2 +-
 services/teamcity/teamcity-base.js            |  4 +--
 services/testspace/testspace-base.js          |  2 +-
 services/tokei/tokei.service.js               |  2 +-
 services/twitch/twitch-base.js                |  2 +-
 services/ubuntu/ubuntu.service.js             |  2 +-
 .../visual-studio-app-center-base.js          |  2 +-
 .../visual-studio-marketplace-base.js         |  2 +-
 .../weblate-component-license.service.js      |  2 +-
 services/weblate/weblate-entities.service.js  |  2 +-
 ...e-project-translated-percentage.service.js |  2 +-
 .../weblate/weblate-user-statistic.service.js |  2 +-
 services/wheelmap/wheelmap.service.js         |  2 +-
 164 files changed, 359 insertions(+), 268 deletions(-)

diff --git a/core/base-service/base-graphql.js b/core/base-service/base-graphql.js
index 694a2f2720..a81bac29f9 100644
--- a/core/base-service/base-graphql.js
+++ b/core/base-service/base-graphql.js
@@ -44,6 +44,12 @@ class BaseGraphqlService extends BaseService {
    *    and custom error messages e.g: `{ 404: 'package not found' }`.
    *    This can be used to extend or override the
    *    [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
+   * @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
+   *    and an object of params to pass when we construct an Inaccessible exception object
+   *    e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
+   *    See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
+   *    for allowed keys
+   *    and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
    * @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
    * further procesing. In case of multiple query in a single graphql call and few of them
    * throw error, partial data might be used ignoring the error.
@@ -62,6 +68,7 @@ class BaseGraphqlService extends BaseService {
     variables = {},
     options = {},
     httpErrorMessages = {},
+    systemErrors = {},
     transformJson = data => data,
     transformErrors = defaultTransformErrors,
   }) {
@@ -74,7 +81,8 @@ class BaseGraphqlService extends BaseService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages: httpErrorMessages,
+      httpErrors: httpErrorMessages,
+      systemErrors,
     })
     const json = transformJson(this._parseJson(buffer))
     if (json.errors) {
diff --git a/core/base-service/base-json.js b/core/base-service/base-json.js
index ebabf1a416..fe42783bd3 100644
--- a/core/base-service/base-json.js
+++ b/core/base-service/base-json.js
@@ -30,14 +30,26 @@ class BaseJsonService extends BaseService {
    * @param {string} attrs.url URL to request
    * @param {object} [attrs.options={}] Options to pass to got. See
    *    [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
-   * @param {object} [attrs.errorMessages={}] Key-value map of status codes
+   * @param {object} [attrs.httpErrors={}] Key-value map of status codes
    *    and custom error messages e.g: `{ 404: 'package not found' }`.
    *    This can be used to extend or override the
    *    [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
+   * @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
+   *    and an object of params to pass when we construct an Inaccessible exception object
+   *    e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
+   *    See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
+   *    for allowed keys
+   *    and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
    * @returns {object} Parsed response
    * @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
    */
-  async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
+  async _requestJson({
+    schema,
+    url,
+    options = {},
+    httpErrors = {},
+    systemErrors = {},
+  }) {
     const mergedOptions = {
       ...{ headers: { Accept: 'application/json' } },
       ...options,
@@ -45,7 +57,8 @@ class BaseJsonService extends BaseService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages,
+      httpErrors,
+      systemErrors,
     })
     const json = this._parseJson(buffer)
     return this.constructor._validate(json, schema)
diff --git a/core/base-service/base-svg-scraping.js b/core/base-service/base-svg-scraping.js
index a1cf981857..95bc3be8ac 100644
--- a/core/base-service/base-svg-scraping.js
+++ b/core/base-service/base-svg-scraping.js
@@ -53,10 +53,16 @@ class BaseSvgScrapingService extends BaseService {
    * @param {string} attrs.url URL to request
    * @param {object} [attrs.options={}] Options to pass to got. See
    *    [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
-   * @param {object} [attrs.errorMessages={}] Key-value map of status codes
+   * @param {object} [attrs.httpErrors={}] Key-value map of status codes
    *    and custom error messages e.g: `{ 404: 'package not found' }`.
    *    This can be used to extend or override the
    *    [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
+   * @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
+   *    and an object of params to pass when we construct an Inaccessible exception object
+   *    e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
+   *    See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
+   *    for allowed keys
+   *    and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
    * @returns {object} Parsed response
    * @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
    */
@@ -65,7 +71,8 @@ class BaseSvgScrapingService extends BaseService {
     valueMatcher,
     url,
     options = {},
-    errorMessages = {},
+    httpErrors = {},
+    systemErrors = {},
   }) {
     const logTrace = (...args) => trace.logTrace('fetch', ...args)
     const mergedOptions = {
@@ -75,7 +82,8 @@ class BaseSvgScrapingService extends BaseService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages,
+      httpErrors,
+      systemErrors,
     })
     logTrace(emojic.dart, 'Response SVG', buffer)
     const data = {
diff --git a/core/base-service/base-xml.js b/core/base-service/base-xml.js
index d758781c39..7395c45ea6 100644
--- a/core/base-service/base-xml.js
+++ b/core/base-service/base-xml.js
@@ -24,10 +24,16 @@ class BaseXmlService extends BaseService {
    * @param {string} attrs.url URL to request
    * @param {object} [attrs.options={}] Options to pass to got. See
    *    [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
-   * @param {object} [attrs.errorMessages={}] Key-value map of status codes
+   * @param {object} [attrs.httpErrors={}] Key-value map of status codes
    *    and custom error messages e.g: `{ 404: 'package not found' }`.
    *    This can be used to extend or override the
    *    [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
+   * @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
+   *    and an object of params to pass when we construct an Inaccessible exception object
+   *    e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
+   *    See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
+   *    for allowed keys
+   *    and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
    * @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
    *    [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
    * @returns {object} Parsed response
@@ -38,7 +44,8 @@ class BaseXmlService extends BaseService {
     schema,
     url,
     options = {},
-    errorMessages = {},
+    httpErrors = {},
+    systemErrors = {},
     parserOptions = {},
   }) {
     const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -49,7 +56,8 @@ class BaseXmlService extends BaseService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages,
+      httpErrors,
+      systemErrors,
     })
     const validateResult = XMLValidator.validate(buffer)
     if (validateResult !== true) {
diff --git a/core/base-service/base-yaml.js b/core/base-service/base-yaml.js
index 85e08f2d25..bae76068f2 100644
--- a/core/base-service/base-yaml.js
+++ b/core/base-service/base-yaml.js
@@ -23,10 +23,16 @@ class BaseYamlService extends BaseService {
    * @param {string} attrs.url URL to request
    * @param {object} [attrs.options={}] Options to pass to got. See
    *    [documentation](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md)
-   * @param {object} [attrs.errorMessages={}] Key-value map of status codes
+   * @param {object} [attrs.httpErrors={}] Key-value map of status codes
    *    and custom error messages e.g: `{ 404: 'package not found' }`.
    *    This can be used to extend or override the
    *    [default](https://github.com/badges/shields/blob/master/core/base-service/check-error-response.js#L5)
+   * @param {object} [attrs.systemErrors={}] Key-value map of got network exception codes
+   *    and an object of params to pass when we construct an Inaccessible exception object
+   *    e.g: `{ ECONNRESET: { prettyMessage: 'connection reset' } }`.
+   *    See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
+   *    for allowed keys
+   *    and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
    * @param {object} [attrs.encoding='utf8'] Character encoding
    * @returns {object} Parsed response
    * @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
@@ -35,7 +41,8 @@ class BaseYamlService extends BaseService {
     schema,
     url,
     options = {},
-    errorMessages = {},
+    httpErrors = {},
+    systemErrors = {},
     encoding = 'utf8',
   }) {
     const logTrace = (...args) => trace.logTrace('fetch', ...args)
@@ -51,7 +58,8 @@ class BaseYamlService extends BaseService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages,
+      httpErrors,
+      systemErrors,
     })
     let parsed
     try {
diff --git a/core/base-service/base.js b/core/base-service/base.js
index f60470fa3b..cf077c562f 100644
--- a/core/base-service/base.js
+++ b/core/base-service/base.js
@@ -226,7 +226,7 @@ class BaseService {
     this._metricHelper = metricHelper
   }
 
-  async _request({ url, options = {}, errorMessages = {} }) {
+  async _request({ url, options = {}, httpErrors = {}, systemErrors = {} }) {
     const logTrace = (...args) => trace.logTrace('fetch', ...args)
     let logUrl = url
     const logOptions = Object.assign({}, options)
@@ -246,10 +246,14 @@ class BaseService {
       'Request',
       `${logUrl}\n${JSON.stringify(logOptions, null, 2)}`
     )
-    const { res, buffer } = await this._requestFetcher(url, options)
+    const { res, buffer } = await this._requestFetcher(
+      url,
+      options,
+      systemErrors
+    )
     await this._meterResponse(res, buffer)
     logTrace(emojic.dart, 'Response status code', res.statusCode)
-    return checkErrorResponse(errorMessages)({ buffer, res })
+    return checkErrorResponse(httpErrors)({ buffer, res })
   }
 
   static enabledMetrics = []
@@ -328,11 +332,15 @@ class BaseService {
       error instanceof Deprecated
     ) {
       trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
-      return {
+      const serviceData = {
         isError: true,
         message: error.prettyMessage,
         color: 'lightgray',
       }
+      if (error.cacheSeconds !== undefined) {
+        serviceData.cacheSeconds = error.cacheSeconds
+      }
+      return serviceData
     } else if (this._handleInternalErrors) {
       if (
         !trace.logTrace(
diff --git a/core/base-service/cache-headers.js b/core/base-service/cache-headers.js
index 14a707efae..2bd02e2f16 100644
--- a/core/base-service/cache-headers.js
+++ b/core/base-service/cache-headers.js
@@ -39,6 +39,7 @@ function coalesceCacheLength({
   assert(defaultCacheLengthSeconds !== undefined)
 
   const cacheLength = coalesce(
+    serviceOverrideCacheLengthSeconds,
     serviceDefaultCacheLengthSeconds,
     defaultCacheLengthSeconds
   )
@@ -46,7 +47,6 @@ function coalesceCacheLength({
   // Overrides can apply _more_ caching, but not less. Query param overriding
   // can request more overriding than service override, but not less.
   const candidateOverrides = [
-    serviceOverrideCacheLengthSeconds,
     overrideCacheLengthFromQueryParams(queryParams),
   ].filter(x => x !== undefined)
 
diff --git a/core/base-service/cache-headers.spec.js b/core/base-service/cache-headers.spec.js
index ebfc53f815..fcf82ee749 100644
--- a/core/base-service/cache-headers.spec.js
+++ b/core/base-service/cache-headers.spec.js
@@ -74,12 +74,12 @@ describe('Cache header functions', function () {
         serviceDefaultCacheLengthSeconds: 900,
         serviceOverrideCacheLengthSeconds: 400,
         queryParams: {},
-      }).expect(900)
+      }).expect(400)
       given({
         cacheHeaderConfig,
         serviceOverrideCacheLengthSeconds: 400,
         queryParams: {},
-      }).expect(777)
+      }).expect(400)
       given({
         cacheHeaderConfig,
         serviceOverrideCacheLengthSeconds: 900,
diff --git a/core/base-service/check-error-response.js b/core/base-service/check-error-response.js
index 93120d725a..cda53fa7e9 100644
--- a/core/base-service/check-error-response.js
+++ b/core/base-service/check-error-response.js
@@ -5,19 +5,19 @@ const defaultErrorMessages = {
   429: 'rate limited by upstream service',
 }
 
-export default function checkErrorResponse(errorMessages = {}) {
+export default function checkErrorResponse(httpErrors = {}) {
   return async function ({ buffer, res }) {
     let error
-    errorMessages = { ...defaultErrorMessages, ...errorMessages }
+    httpErrors = { ...defaultErrorMessages, ...httpErrors }
     if (res.statusCode === 404) {
-      error = new NotFound({ prettyMessage: errorMessages[404] })
+      error = new NotFound({ prettyMessage: httpErrors[404] })
     } else if (res.statusCode !== 200) {
       const underlying = Error(
         `Got status code ${res.statusCode} (expected 200)`
       )
       const props = { underlyingError: underlying }
-      if (errorMessages[res.statusCode] !== undefined) {
-        props.prettyMessage = errorMessages[res.statusCode]
+      if (httpErrors[res.statusCode] !== undefined) {
+        props.prettyMessage = httpErrors[res.statusCode]
       }
       if (res.statusCode >= 500) {
         error = new Inaccessible(props)
diff --git a/core/base-service/errors.js b/core/base-service/errors.js
index a1c897ff6d..ce90f199d2 100644
--- a/core/base-service/errors.js
+++ b/core/base-service/errors.js
@@ -42,6 +42,7 @@ class ShieldsRuntimeError extends Error {
     if (props.underlyingError) {
       this.stack = props.underlyingError.stack
     }
+    this.cacheSeconds = props.cacheSeconds
   }
 }
 
@@ -206,6 +207,9 @@ class Deprecated extends ShieldsRuntimeError {
  * @property {string} prettyMessage User-facing error message to override the
  * value of `defaultPrettyMessage()`. This is the text that will appear on the
  * badge when we catch and render the exception (Optional)
+ * @property {number} cacheSeconds Length of time to cache this error response
+ * for. Defaults to the cacheLength of the service class throwing the error
+ * (Optional)
  */
 
 export {
diff --git a/core/base-service/got.js b/core/base-service/got.js
index 773aa3b57f..793901e7ea 100644
--- a/core/base-service/got.js
+++ b/core/base-service/got.js
@@ -7,7 +7,7 @@ import {
 
 const userAgent = getUserAgent()
 
-async function sendRequest(gotWrapper, url, options) {
+async function sendRequest(gotWrapper, url, options = {}, systemErrors = {}) {
   const gotOptions = Object.assign({}, options)
   gotOptions.throwHttpErrors = false
   gotOptions.retry = { limit: 0 }
@@ -22,6 +22,12 @@ async function sendRequest(gotWrapper, url, options) {
         underlyingError: new Error('Maximum response size exceeded'),
       })
     }
+    if (err.code in systemErrors) {
+      throw new Inaccessible({
+        ...systemErrors[err.code],
+        underlyingError: err,
+      })
+    }
     throw new Inaccessible({ underlyingError: err })
   }
 }
diff --git a/core/base-service/got.spec.js b/core/base-service/got.spec.js
index 004ee667a1..37b201f0f5 100644
--- a/core/base-service/got.spec.js
+++ b/core/base-service/got.spec.js
@@ -45,6 +45,36 @@ describe('got wrapper', function () {
     )
   })
 
+  it('should throw a custom error if provided', async function () {
+    const sendRequest = _fetchFactory(1024)
+    return (
+      expect(
+        sendRequest(
+          'https://www.google.com/foo/bar',
+          { timeout: { request: 1 } },
+          {
+            ETIMEDOUT: {
+              prettyMessage: 'Oh no! A terrible thing has happened',
+              cacheSeconds: 10,
+            },
+          }
+        )
+      )
+        .to.be.rejectedWith(
+          Inaccessible,
+          "Inaccessible: Timeout awaiting 'request' for 1ms"
+        )
+        // eslint-disable-next-line promise/prefer-await-to-then
+        .then(error => {
+          expect(error).to.have.property(
+            'prettyMessage',
+            'Oh no! A terrible thing has happened'
+          )
+          expect(error).to.have.property('cacheSeconds', 10)
+        })
+    )
+  })
+
   it('should pass a custom user agent header', async function () {
     nock('https://www.google.com', {
       reqheaders: {
diff --git a/core/base-service/legacy-request-handler.js b/core/base-service/legacy-request-handler.js
index 2e5b7e92b9..9ef3d4efeb 100644
--- a/core/base-service/legacy-request-handler.js
+++ b/core/base-service/legacy-request-handler.js
@@ -73,11 +73,9 @@ function handleRequest(cacheHeaderConfig, handlerOptions) {
     // `defaultCacheLengthSeconds` can be overridden by
     // `serviceDefaultCacheLengthSeconds` (either by category or on a badge-
     // by-badge basis). Then in turn that can be overridden by
-    // `serviceOverrideCacheLengthSeconds` (which we expect to be used only in
-    // the dynamic badge) but only if `serviceOverrideCacheLengthSeconds` is
-    // longer than `serviceDefaultCacheLengthSeconds` and then the `cacheSeconds`
-    // query param can also override both of those but again only if `cacheSeconds`
-    // is longer.
+    // `serviceOverrideCacheLengthSeconds`.
+    // Then the `cacheSeconds` query param can also override both of those
+    // but only if `cacheSeconds` is longer.
     //
     // When the legacy services have been rewritten, all the code in here
     // will go away, which should achieve this goal in a simpler way.
diff --git a/core/base-service/legacy-request-handler.spec.js b/core/base-service/legacy-request-handler.spec.js
index 2265f18e69..9293e42eca 100644
--- a/core/base-service/legacy-request-handler.spec.js
+++ b/core/base-service/legacy-request-handler.spec.js
@@ -148,7 +148,7 @@ describe('The request handler', function () {
         expect(headers['cache-control']).to.equal('max-age=900, s-maxage=900')
       })
 
-      it('should let live service data override the default cache headers with longer value', async function () {
+      it('should allow serviceData to override the default cache headers with longer value', async function () {
         camp.route(
           /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
           handleRequest(
@@ -168,7 +168,7 @@ describe('The request handler', function () {
         expect(headers['cache-control']).to.equal('max-age=400, s-maxage=400')
       })
 
-      it('should not let live service data override the default cache headers with shorter value', async function () {
+      it('should allow serviceData to override the default cache headers with shorter value', async function () {
         camp.route(
           /^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
           handleRequest(
@@ -185,7 +185,7 @@ describe('The request handler', function () {
         )
 
         const { headers } = await got(`${baseUrl}/testing/123.json`)
-        expect(headers['cache-control']).to.equal('max-age=300, s-maxage=300')
+        expect(headers['cache-control']).to.equal('max-age=200, s-maxage=200')
       })
 
       it('should set the expires header to current time + cacheSeconds', async function () {
diff --git a/doc/TUTORIAL.md b/doc/TUTORIAL.md
index 06441df876..f649f197ce 100644
--- a/doc/TUTORIAL.md
+++ b/doc/TUTORIAL.md
@@ -229,14 +229,14 @@ Description of the code:
 
    - `_requestJson()` automatically adds an Accept header, checks the status code, parses the response as JSON, and returns the parsed response.
    - `_requestJson()` uses [got](https://github.com/sindresorhus/got) to perform the HTTP request. Options can be passed to got, including method, query string, and headers. If headers are provided they will override the ones automatically set by `_requestJson()`. There is no need to specify json, as the JSON parsing is handled by `_requestJson()`. See the `got` docs for [supported options](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md).
-   - Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in `errorMessages`.
+   - Error messages corresponding to each status code can be returned by passing a dictionary of status codes -> messages in `httpErrors`.
    - A more complex call to `_requestJson()` might look like this:
      ```js
      return this._requestJson({
        schema: mySchema,
        url,
        options: { searchParams: { branch: 'master' } },
-       errorMessages: {
+       httpErrors: {
          401: 'private application not supported',
          404: 'application not found',
        },
diff --git a/doc/service-tests.md b/doc/service-tests.md
index 2242920f68..45f5cd8163 100644
--- a/doc/service-tests.md
+++ b/doc/service-tests.md
@@ -152,7 +152,7 @@ npm run test:services -- --only="wercker" --fgrep="Build status (with branch)"
 Having covered the typical and custom cases, we'll move on to errors. We should include a test for the 'not found' response and also tests for any other custom error handling. The Wercker integration defines a custom error condition for 401 as well as a custom 404 message:
 
 ```js
-errorMessages: {
+httpErrors: {
   401: 'private application not supported',
   404: 'application not found',
 }
diff --git a/services/appveyor/appveyor-base.js b/services/appveyor/appveyor-base.js
index 923b718466..9903beae5f 100644
--- a/services/appveyor/appveyor-base.js
+++ b/services/appveyor/appveyor-base.js
@@ -29,7 +29,7 @@ export default class AppVeyorBase extends BaseJsonService {
     return this._requestJson({
       schema,
       url,
-      errorMessages: { 404: 'project not found or access denied' },
+      httpErrors: { 404: 'project not found or access denied' },
     })
   }
 
diff --git a/services/azure-devops/azure-devops-base.js b/services/azure-devops/azure-devops-base.js
index 6b01bea846..85331836d4 100644
--- a/services/azure-devops/azure-devops-base.js
+++ b/services/azure-devops/azure-devops-base.js
@@ -19,13 +19,13 @@ export default class AzureDevOpsBase extends BaseJsonService {
     defaultToEmptyStringForUser: true,
   }
 
-  async fetch({ url, options, schema, errorMessages }) {
+  async fetch({ url, options, schema, httpErrors }) {
     return this._requestJson(
       this.authHelper.withBasicAuth({
         schema,
         url,
         options,
-        errorMessages,
+        httpErrors,
       })
     )
   }
@@ -35,7 +35,7 @@ export default class AzureDevOpsBase extends BaseJsonService {
     project,
     definitionId,
     branch,
-    errorMessages
+    httpErrors
   ) {
     // Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-5.0
     const url = `https://dev.azure.com/${organization}/${project}/_apis/build/builds`
@@ -56,7 +56,7 @@ export default class AzureDevOpsBase extends BaseJsonService {
       url,
       options,
       schema: latestBuildSchema,
-      errorMessages,
+      httpErrors,
     })
 
     if (json.count !== 1) {
diff --git a/services/azure-devops/azure-devops-build.service.js b/services/azure-devops/azure-devops-build.service.js
index a4c2bc93b7..98403df403 100644
--- a/services/azure-devops/azure-devops-build.service.js
+++ b/services/azure-devops/azure-devops-build.service.js
@@ -109,7 +109,7 @@ export default class AzureDevOpsBuild extends BaseSvgScrapingService {
         stageName: stage,
         jobName: job,
       },
-      errorMessages: {
+      httpErrors: {
         404: 'user or project not found',
       },
     })
diff --git a/services/azure-devops/azure-devops-coverage.service.js b/services/azure-devops/azure-devops-coverage.service.js
index eb5b7eeb79..6ab0ad9fe0 100644
--- a/services/azure-devops/azure-devops-coverage.service.js
+++ b/services/azure-devops/azure-devops-coverage.service.js
@@ -88,7 +88,7 @@ export default class AzureDevOpsCoverage extends AzureDevOpsBase {
   }
 
   async handle({ organization, project, definitionId, branch }) {
-    const errorMessages = {
+    const httpErrors = {
       404: 'build pipeline or coverage not found',
     }
     const buildId = await this.getLatestCompletedBuildId(
@@ -96,7 +96,7 @@ export default class AzureDevOpsCoverage extends AzureDevOpsBase {
       project,
       definitionId,
       branch,
-      errorMessages
+      httpErrors
     )
     // Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/azure/devops/test/code%20coverage/get%20build%20code%20coverage?view=azure-devops-rest-5.0
     const url = `https://dev.azure.com/${organization}/${project}/_apis/test/codecoverage`
@@ -110,7 +110,7 @@ export default class AzureDevOpsCoverage extends AzureDevOpsBase {
       url,
       options,
       schema: buildCodeCoverageSchema,
-      errorMessages,
+      httpErrors,
     })
 
     let covered = 0
diff --git a/services/azure-devops/azure-devops-helpers.js b/services/azure-devops/azure-devops-helpers.js
index e57f8c8347..a2a0c89694 100644
--- a/services/azure-devops/azure-devops-helpers.js
+++ b/services/azure-devops/azure-devops-helpers.js
@@ -15,16 +15,13 @@ const schema = Joi.object({
     .required(),
 }).required()
 
-async function fetch(
-  serviceInstance,
-  { url, searchParams = {}, errorMessages }
-) {
+async function fetch(serviceInstance, { url, searchParams = {}, httpErrors }) {
   // Microsoft documentation: https://docs.microsoft.com/en-us/rest/api/vsts/build/status/get
   const { message: status } = await serviceInstance._requestSvg({
     schema,
     url,
     options: { searchParams },
-    errorMessages,
+    httpErrors,
   })
   return { status }
 }
diff --git a/services/azure-devops/azure-devops-release.service.js b/services/azure-devops/azure-devops-release.service.js
index 96393ad4b0..288f62435d 100644
--- a/services/azure-devops/azure-devops-release.service.js
+++ b/services/azure-devops/azure-devops-release.service.js
@@ -49,7 +49,7 @@ export default class AzureDevOpsRelease extends BaseSvgScrapingService {
     // Microsoft documentation: ?
     const props = await fetch(this, {
       url: `https://vsrm.dev.azure.com/${organization}/_apis/public/Release/badge/${projectId}/${definitionId}/${environmentId}`,
-      errorMessages: {
+      httpErrors: {
         400: 'project not found',
         404: 'user or environment not found',
         500: 'inaccessible or definition not found',
diff --git a/services/azure-devops/azure-devops-tests.service.js b/services/azure-devops/azure-devops-tests.service.js
index 8b10ebf431..e27b505f55 100644
--- a/services/azure-devops/azure-devops-tests.service.js
+++ b/services/azure-devops/azure-devops-tests.service.js
@@ -145,7 +145,7 @@ export default class AzureDevOpsTests extends AzureDevOpsBase {
   }
 
   async fetchTestResults({ organization, project, definitionId, branch }) {
-    const errorMessages = {
+    const httpErrors = {
       404: 'build pipeline or test result summary not found',
     }
     const buildId = await this.getLatestCompletedBuildId(
@@ -153,7 +153,7 @@ export default class AzureDevOpsTests extends AzureDevOpsBase {
       project,
       definitionId,
       branch,
-      errorMessages
+      httpErrors
     )
 
     // https://dev.azure.com/azuredevops-powershell/azuredevops-powershell/_apis/test/ResultSummaryByBuild?buildId=20
@@ -163,7 +163,7 @@ export default class AzureDevOpsTests extends AzureDevOpsBase {
         searchParams: { buildId },
       },
       schema: buildTestResultSummarySchema,
-      errorMessages,
+      httpErrors,
     })
   }
 
diff --git a/services/bit/bit-components.service.js b/services/bit/bit-components.service.js
index e223663903..15f27730d4 100644
--- a/services/bit/bit-components.service.js
+++ b/services/bit/bit-components.service.js
@@ -37,7 +37,7 @@ export default class BitComponents extends BaseJsonService {
     return this._requestJson({
       url,
       schema: collectionSchema,
-      errorMessages: {
+      httpErrors: {
         404: 'collection not found',
       },
     })
diff --git a/services/bitbucket/bitbucket-issues.service.js b/services/bitbucket/bitbucket-issues.service.js
index 38eafea3a3..2da5a629cd 100644
--- a/services/bitbucket/bitbucket-issues.service.js
+++ b/services/bitbucket/bitbucket-issues.service.js
@@ -46,7 +46,7 @@ function issueClassGenerator(raw) {
         options: {
           searchParams: { limit: 0, q: '(state = "new" OR state = "open")' },
         },
-        errorMessages: { 403: 'private repo' },
+        httpErrors: { 403: 'private repo' },
       })
     }
 
diff --git a/services/bitbucket/bitbucket-pipelines.service.js b/services/bitbucket/bitbucket-pipelines.service.js
index 29d0277d4b..878a518a3b 100644
--- a/services/bitbucket/bitbucket-pipelines.service.js
+++ b/services/bitbucket/bitbucket-pipelines.service.js
@@ -63,7 +63,7 @@ class BitbucketPipelines extends BaseJsonService {
           'target.ref_name': branch,
         },
       },
-      errorMessages: { 403: 'private repo' },
+      httpErrors: { 403: 'private repo' },
     })
   }
 
diff --git a/services/bitbucket/bitbucket-pull-request.service.js b/services/bitbucket/bitbucket-pull-request.service.js
index b885109fb5..9606e82138 100644
--- a/services/bitbucket/bitbucket-pull-request.service.js
+++ b/services/bitbucket/bitbucket-pull-request.service.js
@@ -12,7 +12,7 @@ const queryParamSchema = Joi.object({
   server: optionalUrl,
 }).required()
 
-const errorMessages = {
+const httpErrors = {
   401: 'invalid credentials',
   403: 'private repo',
   404: 'not found',
@@ -87,7 +87,7 @@ function pullRequestClassGenerator(raw) {
           url: `https://bitbucket.org/api/2.0/repositories/${user}/${repo}/pullrequests/`,
           schema,
           options: { searchParams: { state: 'OPEN', limit: 0 } },
-          errorMessages,
+          httpErrors,
         })
       )
     }
@@ -106,7 +106,7 @@ function pullRequestClassGenerator(raw) {
               withAttributes: false,
             },
           },
-          errorMessages,
+          httpErrors,
         })
       )
     }
diff --git a/services/bitrise/bitrise.service.js b/services/bitrise/bitrise.service.js
index cca2a78666..49e5f2fb90 100644
--- a/services/bitrise/bitrise.service.js
+++ b/services/bitrise/bitrise.service.js
@@ -55,7 +55,7 @@ export default class Bitrise extends BaseJsonService {
       )}/status.json`,
       options: { searchParams: { token, branch } },
       schema,
-      errorMessages: {
+      httpErrors: {
         403: 'app not found or invalid token',
       },
     })
diff --git a/services/bower/bower-base.js b/services/bower/bower-base.js
index 92fb2c1e26..2ead07514a 100644
--- a/services/bower/bower-base.js
+++ b/services/bower/bower-base.js
@@ -22,7 +22,7 @@ export default class BaseBowerService extends LibrariesIoBase {
     return this._requestJson({
       schema,
       url: `/bower/${packageName}`,
-      errorMessages: {
+      httpErrors: {
         404: 'package not found',
       },
     })
diff --git a/services/bundlephobia/bundlephobia.service.js b/services/bundlephobia/bundlephobia.service.js
index c3fed6cdd1..68ac512620 100644
--- a/services/bundlephobia/bundlephobia.service.js
+++ b/services/bundlephobia/bundlephobia.service.js
@@ -84,7 +84,7 @@ export default class Bundlephobia extends BaseJsonService {
       schema,
       url: 'https://bundlephobia.com/api/size',
       options,
-      errorMessages: {
+      httpErrors: {
         404: 'package or version not found',
       },
     })
diff --git a/services/cii-best-practices/cii-best-practices.service.js b/services/cii-best-practices/cii-best-practices.service.js
index d7230334dd..2ee81cec50 100644
--- a/services/cii-best-practices/cii-best-practices.service.js
+++ b/services/cii-best-practices/cii-best-practices.service.js
@@ -115,7 +115,7 @@ export default class CIIBestPracticesService extends BaseJsonService {
       await this._requestJson({
         schema,
         url: `https://bestpractices.coreinfrastructure.org/projects/${projectId}/badge.json`,
-        errorMessages: {
+        httpErrors: {
           404: 'project not found',
         },
       })
diff --git a/services/circleci/circleci.service.js b/services/circleci/circleci.service.js
index 11dd943cd3..49ca87d5d0 100644
--- a/services/circleci/circleci.service.js
+++ b/services/circleci/circleci.service.js
@@ -57,7 +57,7 @@ class CircleCi extends BaseSvgScrapingService {
       // Note that the unusual 'circle-token' query param name is required.
       // https://circleci.com/docs/api/#get-authenticated
       options: { searchParams: { style: 'shield', 'circle-token': token } },
-      errorMessages: { 404: 'project not found' },
+      httpErrors: { 404: 'project not found' },
     })
     return this.constructor.render({ status: message })
   }
diff --git a/services/clearlydefined/clearlydefined-score.service.js b/services/clearlydefined/clearlydefined-score.service.js
index f480581af6..d100df5bcc 100644
--- a/services/clearlydefined/clearlydefined-score.service.js
+++ b/services/clearlydefined/clearlydefined-score.service.js
@@ -53,7 +53,7 @@ export default class ClearlyDefinedService extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://api.clearlydefined.io/definitions/${type}/${provider}/${namespace}/${name}/${revision}`,
-      errorMessages: {
+      httpErrors: {
         500: 'unknown type, provider, or upstream issue',
       },
     })
diff --git a/services/codacy/codacy-coverage.service.js b/services/codacy/codacy-coverage.service.js
index d57c1e80e3..7deae5d34d 100644
--- a/services/codacy/codacy-coverage.service.js
+++ b/services/codacy/codacy-coverage.service.js
@@ -53,7 +53,7 @@ export default class CodacyCoverage extends BaseSvgScrapingService {
       )}`,
       options: { searchParams: { branch } },
       valueMatcher: /text-anchor="middle">([^<>]+)<\/text>/,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/codacy/codacy-grade.service.js b/services/codacy/codacy-grade.service.js
index aeee96579c..afb4ccabcc 100644
--- a/services/codacy/codacy-grade.service.js
+++ b/services/codacy/codacy-grade.service.js
@@ -51,7 +51,7 @@ export default class CodacyGrade extends BaseSvgScrapingService {
         projectId
       )}`,
       options: { searchParams: { branch } },
-      errorMessages: { 404: 'project or branch not found' },
+      httpErrors: { 404: 'project or branch not found' },
       valueMatcher: /visibility="hidden">([^<>]+)<\/text>/,
     })
     return this.constructor.render({ grade })
diff --git a/services/codecov/codecov.service.js b/services/codecov/codecov.service.js
index d6ab261498..6c505854ba 100644
--- a/services/codecov/codecov.service.js
+++ b/services/codecov/codecov.service.js
@@ -117,7 +117,7 @@ export default class Codecov extends BaseSvgScrapingService {
           Authorization: `token ${token}`,
         },
       },
-      errorMessages: {
+      httpErrors: {
         401: 'not authorized to access repository',
         404: 'repository not found',
       },
@@ -153,7 +153,7 @@ export default class Codecov extends BaseSvgScrapingService {
       options: {
         searchParams: { token, flag },
       },
-      errorMessages: token ? { 400: 'invalid token pattern' } : {},
+      httpErrors: token ? { 400: 'invalid token pattern' } : {},
     })
   }
 
diff --git a/services/codefactor/codefactor-grade.service.js b/services/codefactor/codefactor-grade.service.js
index 70528574be..0752654565 100644
--- a/services/codefactor/codefactor-grade.service.js
+++ b/services/codefactor/codefactor-grade.service.js
@@ -41,7 +41,7 @@ export default class CodeFactorGrade extends BaseSvgScrapingService {
       url: `https://codefactor.io/repository/${vcsType}/${user}/${repo}/badge/${
         branch || ''
       }`,
-      errorMessages: { 404: 'repo or branch not found' },
+      httpErrors: { 404: 'repo or branch not found' },
     })
     return this.constructor.render({ grade: message })
   }
diff --git a/services/coincap/coincap-base.js b/services/coincap/coincap-base.js
index 7ad3dfb6c6..d602fb2529 100644
--- a/services/coincap/coincap-base.js
+++ b/services/coincap/coincap-base.js
@@ -12,7 +12,7 @@ export default class BaseCoincapService extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://api.coincap.io/v2/assets/${assetId}`,
-      errorMessages: {
+      httpErrors: {
         404: 'asset not found',
       },
     })
diff --git a/services/coveralls/coveralls.service.js b/services/coveralls/coveralls.service.js
index 9f20c419be..1b2b412f35 100644
--- a/services/coveralls/coveralls.service.js
+++ b/services/coveralls/coveralls.service.js
@@ -66,7 +66,7 @@ export default class Coveralls extends BaseJsonService {
       schema,
       url,
       options,
-      errorMessages: {
+      httpErrors: {
         404: 'repository not found',
       },
     })
diff --git a/services/coverity/coverity-scan.service.js b/services/coverity/coverity-scan.service.js
index 2c1e936236..7f6a74210b 100644
--- a/services/coverity/coverity-scan.service.js
+++ b/services/coverity/coverity-scan.service.js
@@ -48,7 +48,7 @@ export default class CoverityScan extends BaseJsonService {
     const json = await this._requestJson({
       url,
       schema,
-      errorMessages: {
+      httpErrors: {
         // At the moment Coverity returns an HTTP 200 with an HTML page
         // displaying the text 404 when project is not found.
         404: 'project not found',
diff --git a/services/discord/discord.service.js b/services/discord/discord.service.js
index 65837b9a35..5fe8944be1 100644
--- a/services/discord/discord.service.js
+++ b/services/discord/discord.service.js
@@ -63,7 +63,7 @@ export default class Discord extends BaseJsonService {
         {
           url,
           schema,
-          errorMessages: {
+          httpErrors: {
             404: 'invalid server',
             403: 'widget disabled',
           },
diff --git a/services/docker/docker-automated.service.js b/services/docker/docker-automated.service.js
index ba28af2990..c5432f8134 100644
--- a/services/docker/docker-automated.service.js
+++ b/services/docker/docker-automated.service.js
@@ -40,7 +40,7 @@ export default class DockerAutomatedBuild extends BaseJsonService {
       url: `https://registry.hub.docker.com/v2/repositories/${getDockerHubUser(
         user
       )}/${repo}`,
-      errorMessages: { 404: 'repo not found' },
+      httpErrors: { 404: 'repo not found' },
     })
   }
 
diff --git a/services/docker/docker-cloud-common-fetch.js b/services/docker/docker-cloud-common-fetch.js
index 51d763261e..bea59e66d3 100644
--- a/services/docker/docker-cloud-common-fetch.js
+++ b/services/docker/docker-cloud-common-fetch.js
@@ -16,7 +16,7 @@ async function fetchBuild(serviceInstance, { user, repo }) {
     schema: cloudBuildSchema,
     url: 'https://cloud.docker.com/api/build/v1/source',
     options: { searchParams: { image: `${user}/${repo}` } },
-    errorMessages: { 404: 'repo not found' },
+    httpErrors: { 404: 'repo not found' },
   })
 }
 
diff --git a/services/docker/docker-pulls.service.js b/services/docker/docker-pulls.service.js
index 4a31a16843..e1a8e8a129 100644
--- a/services/docker/docker-pulls.service.js
+++ b/services/docker/docker-pulls.service.js
@@ -38,7 +38,7 @@ export default class DockerPulls extends BaseJsonService {
       url: `https://hub.docker.com/v2/repositories/${getDockerHubUser(
         user
       )}/${repo}`,
-      errorMessages: { 404: 'repo not found' },
+      httpErrors: { 404: 'repo not found' },
     })
   }
 
diff --git a/services/docker/docker-size.service.js b/services/docker/docker-size.service.js
index 4d4c5f6a6a..bef483105c 100644
--- a/services/docker/docker-size.service.js
+++ b/services/docker/docker-size.service.js
@@ -107,7 +107,7 @@ export default class DockerSize extends BaseJsonService {
       )}/${repo}/tags${
         tag ? `/${tag}` : '?page_size=100&ordering=last_updated'
       }${page}`,
-      errorMessages: { 404: 'repository or tag not found' },
+      httpErrors: { 404: 'repository or tag not found' },
     })
   }
 
diff --git a/services/docker/docker-stars.service.js b/services/docker/docker-stars.service.js
index f7e645aba1..49a43e5d61 100644
--- a/services/docker/docker-stars.service.js
+++ b/services/docker/docker-stars.service.js
@@ -41,7 +41,7 @@ export default class DockerStars extends BaseJsonService {
       url: `https://hub.docker.com/v2/repositories/${getDockerHubUser(
         user
       )}/${repo}/`,
-      errorMessages: { 404: 'repo not found' },
+      httpErrors: { 404: 'repo not found' },
     })
   }
 
diff --git a/services/docker/docker-version.service.js b/services/docker/docker-version.service.js
index 594c8e9647..e61ff42a42 100644
--- a/services/docker/docker-version.service.js
+++ b/services/docker/docker-version.service.js
@@ -69,7 +69,7 @@ export default class DockerVersion extends BaseJsonService {
       url: `https://registry.hub.docker.com/v2/repositories/${getDockerHubUser(
         user
       )}/${repo}/tags?page_size=100&ordering=last_updated${page}`,
-      errorMessages: { 404: 'repository or tag not found' },
+      httpErrors: { 404: 'repository or tag not found' },
     })
   }
 
diff --git a/services/drone/drone-build.service.js b/services/drone/drone-build.service.js
index 3951fd84da..74132b6046 100644
--- a/services/drone/drone-build.service.js
+++ b/services/drone/drone-build.service.js
@@ -75,7 +75,7 @@ export default class DroneBuild extends BaseJsonService {
         options: {
           searchParams: { ref: branch ? `refs/heads/${branch}` : undefined },
         },
-        errorMessages: {
+        httpErrors: {
           401: 'repo not found or not authorized',
         },
       })
diff --git a/services/dynamic-common.js b/services/dynamic-common.js
index d82faaf811..04a68e78cd 100644
--- a/services/dynamic-common.js
+++ b/services/dynamic-common.js
@@ -14,7 +14,7 @@ import { InvalidResponse } from './index.js'
  *
  * @type {object}
  */
-const errorMessages = {
+const httpErrors = {
   404: 'resource not found',
 }
 
@@ -93,7 +93,7 @@ function renderDynamicBadge({
 }
 
 export {
-  errorMessages,
+  httpErrors,
   individualValueSchema,
   transformAndValidate,
   renderDynamicBadge,
diff --git a/services/dynamic/dynamic-json.service.js b/services/dynamic/dynamic-json.service.js
index 0dc3fa56a8..68ffd4f6dc 100644
--- a/services/dynamic/dynamic-json.service.js
+++ b/services/dynamic/dynamic-json.service.js
@@ -54,11 +54,11 @@ export default class DynamicJson extends jsonPath(BaseJsonService) {
     },
   }
 
-  async fetch({ schema, url, errorMessages }) {
+  async fetch({ schema, url, httpErrors }) {
     return this._requestJson({
       schema,
       url,
-      errorMessages,
+      httpErrors,
     })
   }
 }
diff --git a/services/dynamic/dynamic-xml.service.js b/services/dynamic/dynamic-xml.service.js
index 3426af989b..fd4216c5a0 100644
--- a/services/dynamic/dynamic-xml.service.js
+++ b/services/dynamic/dynamic-xml.service.js
@@ -1,7 +1,7 @@
 import { DOMParser } from '@xmldom/xmldom'
 import xpath from 'xpath'
 import { MetricNames } from '../../core/base-service/metric-helper.js'
-import { renderDynamicBadge, errorMessages } from '../dynamic-common.js'
+import { renderDynamicBadge, httpErrors } from '../dynamic-common.js'
 import { BaseService, InvalidResponse, InvalidParameter } from '../index.js'
 import { createRoute } from './dynamic-helpers.js'
 
@@ -113,7 +113,7 @@ export default class DynamicXml extends BaseService {
     const { buffer } = await this._request({
       url,
       options: { headers: { Accept: 'application/xml, text/xml' } },
-      errorMessages,
+      httpErrors,
     })
 
     const { values: value } = this.transform({
diff --git a/services/dynamic/dynamic-yaml.service.js b/services/dynamic/dynamic-yaml.service.js
index 46f84dd8e0..c1a5ab29ce 100644
--- a/services/dynamic/dynamic-yaml.service.js
+++ b/services/dynamic/dynamic-yaml.service.js
@@ -54,11 +54,11 @@ export default class DynamicYaml extends jsonPath(BaseYamlService) {
     },
   }
 
-  async fetch({ schema, url, errorMessages }) {
+  async fetch({ schema, url, httpErrors }) {
     return this._requestYaml({
       schema,
       url,
-      errorMessages,
+      httpErrors,
     })
   }
 }
diff --git a/services/dynamic/json-path.js b/services/dynamic/json-path.js
index ed242c944a..64c45d1578 100644
--- a/services/dynamic/json-path.js
+++ b/services/dynamic/json-path.js
@@ -4,7 +4,7 @@
 
 import Joi from 'joi'
 import jp from 'jsonpath'
-import { renderDynamicBadge, errorMessages } from '../dynamic-common.js'
+import { renderDynamicBadge, httpErrors } from '../dynamic-common.js'
 import { InvalidParameter, InvalidResponse } from '../index.js'
 
 /**
@@ -24,13 +24,13 @@ export default superclass =>
      * @param {object} attrs Refer to individual attrs
      * @param {Joi} attrs.schema Joi schema to validate the response transformed to JSON
      * @param {string} attrs.url URL to request
-     * @param {object} [attrs.errorMessages={}] Key-value map of status codes
+     * @param {object} [attrs.httpErrors={}] Key-value map of status codes
      *    and custom error messages e.g: `{ 404: 'package not found' }`.
      *    This can be used to extend or override the
      *    [default](https://github.com/badges/shields/blob/master/services/dynamic-common.js#L8)
      * @returns {object} Parsed response
      */
-    async fetch({ schema, url, errorMessages }) {
+    async fetch({ schema, url, httpErrors }) {
       throw new Error(
         `fetch() function not implemented for ${this.constructor.name}`
       )
@@ -40,7 +40,7 @@ export default superclass =>
       const data = await this.fetch({
         schema: Joi.any(),
         url,
-        errorMessages,
+        httpErrors,
       })
 
       // JSONPath only works on objects and arrays.
diff --git a/services/eclipse-marketplace/eclipse-marketplace-base.js b/services/eclipse-marketplace/eclipse-marketplace-base.js
index a4276731b9..2324219ef2 100644
--- a/services/eclipse-marketplace/eclipse-marketplace-base.js
+++ b/services/eclipse-marketplace/eclipse-marketplace-base.js
@@ -12,7 +12,7 @@ export default class EclipseMarketplaceBase extends BaseXmlService {
     return this._requestXml({
       schema,
       url: `https://marketplace.eclipse.org/content/${name}/api/p`,
-      errorMessages: { 404: 'solution not found' },
+      httpErrors: { 404: 'solution not found' },
     })
   }
 }
diff --git a/services/ecologi/ecologi-carbon.service.js b/services/ecologi/ecologi-carbon.service.js
index 4aac425d1e..01d878780f 100644
--- a/services/ecologi/ecologi-carbon.service.js
+++ b/services/ecologi/ecologi-carbon.service.js
@@ -30,7 +30,7 @@ export default class EcologiCarbonOffset extends BaseJsonService {
     return this._requestJson({
       url,
       schema: apiSchema,
-      errorMessages: {
+      httpErrors: {
         404: 'username not found',
       },
     })
diff --git a/services/ecologi/ecologi-trees.service.js b/services/ecologi/ecologi-trees.service.js
index 46d41b2f00..0d4225bb17 100644
--- a/services/ecologi/ecologi-trees.service.js
+++ b/services/ecologi/ecologi-trees.service.js
@@ -30,7 +30,7 @@ export default class EcologiTrees extends BaseJsonService {
     return this._requestJson({
       url,
       schema: apiSchema,
-      errorMessages: {
+      httpErrors: {
         404: 'username not found',
       },
     })
diff --git a/services/elm-package/elm-package.service.js b/services/elm-package/elm-package.service.js
index dd606d090c..cf608fd597 100644
--- a/services/elm-package/elm-package.service.js
+++ b/services/elm-package/elm-package.service.js
@@ -27,7 +27,7 @@ export default class ElmPackage extends BaseJsonService {
     const { version } = await this._requestJson({
       schema,
       url,
-      errorMessages: {
+      httpErrors: {
         404: 'package not found',
       },
     })
diff --git a/services/endpoint-common.js b/services/endpoint-common.js
index 5b5c998281..164762fd82 100644
--- a/services/endpoint-common.js
+++ b/services/endpoint-common.js
@@ -82,19 +82,19 @@ const anySchema = Joi.any()
  * @param {object} serviceInstance Instance of Endpoint class
  * @param {object} attrs Refer to individual attributes
  * @param {string} attrs.url Endpoint URL
- * @param {object} attrs.errorMessages Object containing error messages for different error codes
+ * @param {object} attrs.httpErrors Object containing error messages for different error codes
  * @param {string} attrs.validationPrettyErrorMessage If provided then the error message is set to this value
  * @param {boolean} attrs.includeKeys If true then includes error details in error message
  * @returns {object} Data fetched from endpoint
  */
 async function fetchEndpointData(
   serviceInstance,
-  { url, errorMessages, validationPrettyErrorMessage, includeKeys }
+  { url, httpErrors, validationPrettyErrorMessage, includeKeys }
 ) {
   const json = await serviceInstance._requestJson({
     schema: anySchema,
     url,
-    errorMessages,
+    httpErrors,
     options: { decompress: true },
   })
   return validateEndpointData(json, {
diff --git a/services/endpoint/endpoint.service.js b/services/endpoint/endpoint.service.js
index 26be6f8402..10fc4e0174 100644
--- a/services/endpoint/endpoint.service.js
+++ b/services/endpoint/endpoint.service.js
@@ -1,6 +1,6 @@
 import { URL } from 'url'
 import Joi from 'joi'
-import { errorMessages } from '../dynamic-common.js'
+import { httpErrors } from '../dynamic-common.js'
 import { optionalUrl } from '../validators.js'
 import { fetchEndpointData } from '../endpoint-common.js'
 import { BaseJsonService, InvalidParameter } from '../index.js'
@@ -178,7 +178,10 @@ export default class Endpoint extends BaseJsonService {
       logoWidth,
       logoPosition,
       style,
-      cacheSeconds,
+      // don't allow the user to set cacheSeconds any shorter than this._cacheLength
+      cacheSeconds: Math.max(
+        ...[this._cacheLength, cacheSeconds].filter(x => x !== undefined)
+      ),
     }
   }
 
@@ -200,7 +203,7 @@ export default class Endpoint extends BaseJsonService {
 
     const validated = await fetchEndpointData(this, {
       url,
-      errorMessages,
+      httpErrors,
       validationPrettyErrorMessage: 'invalid properties',
       includeKeys: true,
     })
diff --git a/services/f-droid/f-droid.service.js b/services/f-droid/f-droid.service.js
index 3ce765eb20..33de691533 100644
--- a/services/f-droid/f-droid.service.js
+++ b/services/f-droid/f-droid.service.js
@@ -53,7 +53,7 @@ export default class FDroid extends BaseJsonService {
     return this._requestJson({
       schema,
       url,
-      errorMessages: {
+      httpErrors: {
         403: 'app not found',
         404: 'app not found',
       },
diff --git a/services/factorio-mod-portal/factorio-mod-portal.service.js b/services/factorio-mod-portal/factorio-mod-portal.service.js
index 27217d267e..904a7f4a68 100644
--- a/services/factorio-mod-portal/factorio-mod-portal.service.js
+++ b/services/factorio-mod-portal/factorio-mod-portal.service.js
@@ -29,7 +29,7 @@ class BaseFactorioModPortalService extends BaseJsonService {
     const { releases, downloads_count } = await this._requestJson({
       schema,
       url: `https://mods.factorio.com/api/mods/${modName}`,
-      errorMessages: {
+      httpErrors: {
         404: 'mod not found',
       },
     })
diff --git a/services/fedora/fedora.service.js b/services/fedora/fedora.service.js
index d869bcd6ef..10c7ee0163 100644
--- a/services/fedora/fedora.service.js
+++ b/services/fedora/fedora.service.js
@@ -30,7 +30,7 @@ export default class Fedora extends BaseJsonService {
       url: `https://apps.fedoraproject.org/mdapi/${encodeURIComponent(
         branch
       )}/pkg/${encodeURIComponent(packageName)}`,
-      errorMessages: {
+      httpErrors: {
         400: 'branch not found',
       },
     })
diff --git a/services/feedz/feedz.service.js b/services/feedz/feedz.service.js
index 9039f1bf9a..8bd2309194 100644
--- a/services/feedz/feedz.service.js
+++ b/services/feedz/feedz.service.js
@@ -75,7 +75,7 @@ class FeedzVersionService extends BaseJsonService {
     return await this._requestJson({
       schema: packageSchema,
       url: `${registrationsBaseUrl}${packageName}/index.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'repository or package not found',
       },
     })
@@ -90,7 +90,7 @@ class FeedzVersionService extends BaseJsonService {
           this._requestJson({
             schema: singlePageSchema,
             url: i['@id'],
-            errorMessages: {
+            httpErrors: {
               404: 'repository or package not found',
             },
           })
diff --git a/services/gem/gem-downloads.service.js b/services/gem/gem-downloads.service.js
index 6028530a24..6944fe406b 100644
--- a/services/gem/gem-downloads.service.js
+++ b/services/gem/gem-downloads.service.js
@@ -88,7 +88,7 @@ export default class GemDownloads extends BaseJsonService {
     const json = await this._requestJson({
       url: `https://rubygems.org/api/v1/versions/${gem}.json`,
       schema: versionSchema,
-      errorMessages: {
+      httpErrors: {
         404: 'gem not found',
       },
     })
@@ -117,7 +117,7 @@ export default class GemDownloads extends BaseJsonService {
       await this._requestJson({
         url: `https://rubygems.org/api/v1/gems/${gem}.json`,
         schema: gemSchema,
-        errorMessages: {
+        httpErrors: {
           404: 'gem not found',
         },
       })
diff --git a/services/gerrit/gerrit.service.js b/services/gerrit/gerrit.service.js
index 04ebca3757..b6271d588a 100644
--- a/services/gerrit/gerrit.service.js
+++ b/services/gerrit/gerrit.service.js
@@ -64,7 +64,7 @@ export default class Gerrit extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `${baseUrl}/changes/${changeId}`,
-      errorMessages: {
+      httpErrors: {
         404: 'change not found',
       },
     })
diff --git a/services/github/gist/github-gist-last-commit.service.js b/services/github/gist/github-gist-last-commit.service.js
index cc148a8550..db5c8089cd 100644
--- a/services/github/gist/github-gist-last-commit.service.js
+++ b/services/github/gist/github-gist-last-commit.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { formatDate } from '../../text-formatters.js'
 import { age as ageColor } from '../../color-formatters.js'
 import { GithubAuthV3Service } from '../github-auth-service.js'
-import { documentation, errorMessagesFor } from '../github-helpers.js'
+import { documentation, httpErrorsFor } from '../github-helpers.js'
 
 const schema = Joi.object({
   updated_at: Joi.string().required(),
@@ -36,7 +36,7 @@ export default class GistLastCommit extends GithubAuthV3Service {
     return this._requestJson({
       url: `/gists/${gistId}`,
       schema,
-      errorMessages: errorMessagesFor('gist not found'),
+      httpErrors: httpErrorsFor('gist not found'),
     })
   }
 
diff --git a/services/github/github-actions-workflow-status.service.js b/services/github/github-actions-workflow-status.service.js
index 2c598cc3f5..e3f757fcc6 100644
--- a/services/github/github-actions-workflow-status.service.js
+++ b/services/github/github-actions-workflow-status.service.js
@@ -85,7 +85,7 @@ export default class GithubActionsWorkflowStatus extends BaseSvgScrapingService
       )}/badge.svg`,
       options: { searchParams: { branch, event } },
       valueMatcher: />([^<>]+)<\/tspan><\/text><\/g><path/,
-      errorMessages: {
+      httpErrors: {
         404: 'repo or workflow not found',
       },
     })
diff --git a/services/github/github-checks-status.service.js b/services/github/github-checks-status.service.js
index 35097f7b5a..65d7c0a20e 100644
--- a/services/github/github-checks-status.service.js
+++ b/services/github/github-checks-status.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   state: isBuildStatus,
@@ -61,7 +61,7 @@ export default class GithubChecksStatus extends GithubAuthV3Service {
   async handle({ user, repo, ref }) {
     const { state } = await this._requestJson({
       url: `/repos/${user}/${repo}/commits/${ref}/status`,
-      errorMessages: errorMessagesFor('ref or repo not found'),
+      httpErrors: httpErrorsFor('ref or repo not found'),
       schema,
     })
 
diff --git a/services/github/github-commit-status.service.js b/services/github/github-commit-status.service.js
index 46aae3410a..ba83b9f143 100644
--- a/services/github/github-commit-status.service.js
+++ b/services/github/github-commit-status.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { NotFound, InvalidParameter } from '../index.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   // https://stackoverflow.com/a/23969867/893113
@@ -55,7 +55,7 @@ export default class GithubCommitStatus extends GithubAuthV3Service {
     try {
       ;({ status } = await this._requestJson({
         url: `/repos/${user}/${repo}/compare/${branch}...${commit}`,
-        errorMessages: errorMessagesFor('commit or branch not found'),
+        httpErrors: httpErrorsFor('commit or branch not found'),
         schema,
       }))
     } catch (e) {
diff --git a/services/github/github-commits-difference.service.js b/services/github/github-commits-difference.service.js
index c9b867e6db..b5028edf0a 100644
--- a/services/github/github-commits-difference.service.js
+++ b/services/github/github-commits-difference.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({ total_commits: nonNegativeInteger }).required()
 
@@ -51,7 +51,7 @@ export default class GithubCommitsDifference extends GithubAuthV3Service {
     const { total_commits: commitCount } = await this._requestJson({
       schema,
       url: `/repos/${user}/${repo}/compare/${base}...${head}`,
-      errorMessages: errorMessagesFor(notFoundMessage),
+      httpErrors: httpErrorsFor(notFoundMessage),
     })
 
     return this.constructor.render({ commitCount })
diff --git a/services/github/github-commits-since.service.js b/services/github/github-commits-since.service.js
index 7fd0cc924b..8ea3300253 100644
--- a/services/github/github-commits-since.service.js
+++ b/services/github/github-commits-since.service.js
@@ -6,7 +6,7 @@ import {
   fetchLatestRelease,
   queryParamSchema,
 } from './github-common-release.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({ ahead_by: nonNegativeInteger }).required()
 
@@ -151,7 +151,7 @@ export default class GithubCommitsSince extends GithubAuthV3Service {
     const { ahead_by: commitCount } = await this._requestJson({
       schema,
       url: `/repos/${user}/${repo}/compare/${version}...${branch || 'HEAD'}`,
-      errorMessages: errorMessagesFor(notFoundMessage),
+      httpErrors: httpErrorsFor(notFoundMessage),
     })
 
     return this.constructor.render({ version, commitCount })
diff --git a/services/github/github-common-fetch.js b/services/github/github-common-fetch.js
index 25c9a8de19..40784b4694 100644
--- a/services/github/github-common-fetch.js
+++ b/services/github/github-common-fetch.js
@@ -1,6 +1,6 @@
 import Joi from 'joi'
 import { InvalidResponse } from '../index.js'
-import { errorMessagesFor } from './github-helpers.js'
+import { httpErrorsFor } from './github-helpers.js'
 
 const issueSchema = Joi.object({
   head: Joi.object({
@@ -12,7 +12,7 @@ async function fetchIssue(serviceInstance, { user, repo, number }) {
   return serviceInstance._requestJson({
     schema: issueSchema,
     url: `/repos/${user}/${repo}/pulls/${number}`,
-    errorMessages: errorMessagesFor('pull request or repo not found'),
+    httpErrors: httpErrorsFor('pull request or repo not found'),
   })
 }
 
@@ -26,7 +26,7 @@ async function fetchRepoContent(
   serviceInstance,
   { user, repo, branch = 'HEAD', filename }
 ) {
-  const errorMessages = errorMessagesFor(
+  const httpErrors = httpErrorsFor(
     `repo not found, branch not found, or ${filename} missing`
   )
   if (serviceInstance.staticAuthConfigured) {
@@ -34,7 +34,7 @@ async function fetchRepoContent(
       schema: contentSchema,
       url: `/repos/${user}/${repo}/contents/${filename}`,
       options: { searchParams: { ref: branch } },
-      errorMessages,
+      httpErrors,
     })
 
     try {
@@ -45,7 +45,7 @@ async function fetchRepoContent(
   } else {
     const { buffer } = await serviceInstance._request({
       url: `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filename}`,
-      errorMessages,
+      httpErrors,
     })
     return buffer
   }
@@ -68,7 +68,7 @@ async function fetchJsonFromRepo(
     return serviceInstance._requestJson({
       schema,
       url: `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filename}`,
-      errorMessages: errorMessagesFor(
+      httpErrors: httpErrorsFor(
         `repo not found, branch not found, or ${filename} missing`
       ),
     })
diff --git a/services/github/github-common-release.js b/services/github/github-common-release.js
index 991857fa02..e6c9249310 100644
--- a/services/github/github-common-release.js
+++ b/services/github/github-common-release.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { nonNegativeInteger } from '../validators.js'
 import { latest } from '../version.js'
 import { NotFound } from '../index.js'
-import { errorMessagesFor } from './github-helpers.js'
+import { httpErrorsFor } from './github-helpers.js'
 
 const releaseInfoSchema = Joi.object({
   assets: Joi.array()
@@ -19,7 +19,7 @@ const releaseInfoSchema = Joi.object({
 // Fetch the 'latest' release as defined by the GitHub API
 async function fetchLatestGitHubRelease(serviceInstance, { user, repo }) {
   const commonAttrs = {
-    errorMessages: errorMessagesFor('no releases or repo not found'),
+    httpErrors: httpErrorsFor('no releases or repo not found'),
   }
   const releaseInfo = await serviceInstance._requestJson({
     schema: releaseInfoSchema,
@@ -36,7 +36,7 @@ const releaseInfoArraySchema = Joi.alternatives().try(
 
 async function fetchReleases(serviceInstance, { user, repo }) {
   const commonAttrs = {
-    errorMessages: errorMessagesFor('repo not found'),
+    httpErrors: httpErrorsFor('repo not found'),
   }
   const releases = await serviceInstance._requestJson({
     url: `/repos/${user}/${repo}/releases`,
diff --git a/services/github/github-contributors.service.js b/services/github/github-contributors.service.js
index af9b2c6dd1..72162af27a 100644
--- a/services/github/github-contributors.service.js
+++ b/services/github/github-contributors.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import parseLinkHeader from 'parse-link-header'
 import { renderContributorBadge } from '../contributor-count.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 // All we do is check its length.
 const schema = Joi.array().items(Joi.object())
@@ -39,7 +39,7 @@ export default class GithubContributors extends GithubAuthV3Service {
     const { res, buffer } = await this._request({
       url: `/repos/${user}/${repo}/contributors`,
       options: { searchParams: { page: '1', per_page: '1', anon: isAnon } },
-      errorMessages: errorMessagesFor('repo not found'),
+      httpErrors: httpErrorsFor('repo not found'),
     })
 
     const parsed = parseLinkHeader(res.headers.link)
diff --git a/services/github/github-downloads.service.js b/services/github/github-downloads.service.js
index a70d0c1ada..afe597b50b 100644
--- a/services/github/github-downloads.service.js
+++ b/services/github/github-downloads.service.js
@@ -4,7 +4,7 @@ import { renderDownloadsBadge } from '../downloads.js'
 import { NotFound } from '../index.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import { fetchLatestRelease } from './github-common-release.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const queryParamSchema = Joi.object({
   sort: Joi.string().valid('date', 'semver').default('date'),
@@ -233,7 +233,7 @@ export default class GithubDownloads extends GithubAuthV3Service {
       const wantedRelease = await this._requestJson({
         schema: releaseSchema,
         url: `/repos/${user}/${repo}/releases/tags/${tag}`,
-        errorMessages: errorMessagesFor('repo or release not found'),
+        httpErrors: httpErrorsFor('repo or release not found'),
       })
       releases = [wantedRelease]
     } else {
@@ -241,7 +241,7 @@ export default class GithubDownloads extends GithubAuthV3Service {
         schema: releaseArraySchema,
         url: `/repos/${user}/${repo}/releases`,
         options: { searchParams: { per_page: 500 } },
-        errorMessages: errorMessagesFor('repo not found'),
+        httpErrors: httpErrorsFor('repo not found'),
       })
       releases = allReleases
     }
diff --git a/services/github/github-followers.service.js b/services/github/github-followers.service.js
index 71b3d01b05..f0cb3df95f 100644
--- a/services/github/github-followers.service.js
+++ b/services/github/github-followers.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   followers: nonNegativeInteger,
@@ -37,7 +37,7 @@ export default class GithubFollowers extends GithubAuthV3Service {
     const { followers } = await this._requestJson({
       url: `/users/${user}`,
       schema,
-      errorMessages: errorMessagesFor('user not found'),
+      httpErrors: httpErrorsFor('user not found'),
     })
     return this.constructor.render({ followers })
   }
diff --git a/services/github/github-helpers.js b/services/github/github-helpers.js
index d294d659f2..3c9eb81a2b 100644
--- a/services/github/github-helpers.js
+++ b/services/github/github-helpers.js
@@ -14,7 +14,7 @@ function issueStateColor(s) {
   return { open: '2cbe4e', closed: '6f42c1' }[s]
 }
 
-function errorMessagesFor(notFoundMessage = 'repo not found') {
+function httpErrorsFor(notFoundMessage = 'repo not found') {
   return {
     404: notFoundMessage,
     422: notFoundMessage,
@@ -35,6 +35,6 @@ export {
   documentation,
   issueStateColor,
   commentsColor,
-  errorMessagesFor,
+  httpErrorsFor,
   transformErrors,
 }
diff --git a/services/github/github-issue-detail.service.js b/services/github/github-issue-detail.service.js
index a9cb4dadf1..be8cce32be 100644
--- a/services/github/github-issue-detail.service.js
+++ b/services/github/github-issue-detail.service.js
@@ -6,7 +6,7 @@ import { InvalidResponse } from '../index.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import {
   documentation,
-  errorMessagesFor,
+  httpErrorsFor,
   issueStateColor,
   commentsColor,
 } from './github-helpers.js'
@@ -222,7 +222,7 @@ export default class GithubIssueDetail extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}/${issueKind}/${number}`,
       schema: propertyMap[property].schema,
-      errorMessages: errorMessagesFor('issue, pull request or repo not found'),
+      httpErrors: httpErrorsFor('issue, pull request or repo not found'),
     })
   }
 
diff --git a/services/github/github-labels.service.js b/services/github/github-labels.service.js
index b65af30073..4e457da853 100644
--- a/services/github/github-labels.service.js
+++ b/services/github/github-labels.service.js
@@ -1,6 +1,6 @@
 import Joi from 'joi'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   color: Joi.string().hex().required(),
@@ -35,7 +35,7 @@ export default class GithubLabels extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}/labels/${name}`,
       schema,
-      errorMessages: errorMessagesFor('repo or label not found'),
+      httpErrors: httpErrorsFor('repo or label not found'),
     })
   }
 
diff --git a/services/github/github-languages-base.js b/services/github/github-languages-base.js
index 92427519fe..667f12a47e 100644
--- a/services/github/github-languages-base.js
+++ b/services/github/github-languages-base.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { errorMessagesFor } from './github-helpers.js'
+import { httpErrorsFor } from './github-helpers.js'
 
 /*
 We're expecting a response like { "Python": 39624, "Shell": 104 }
@@ -14,7 +14,7 @@ class BaseGithubLanguage extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}/languages`,
       schema,
-      errorMessages: errorMessagesFor(),
+      httpErrors: httpErrorsFor(),
     })
   }
 
diff --git a/services/github/github-last-commit.service.js b/services/github/github-last-commit.service.js
index 45b6840bf8..b5ea3b087c 100644
--- a/services/github/github-last-commit.service.js
+++ b/services/github/github-last-commit.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { formatDate } from '../text-formatters.js'
 import { age as ageColor } from '../color-formatters.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 const commonExampleAttrs = {
   keywords: ['latest'],
   documentation,
@@ -87,7 +87,7 @@ export default class GithubLastCommit extends GithubAuthV3Service {
       url: `/repos/${user}/${repo}/commits`,
       options: { searchParams: { sha: branch } },
       schema,
-      errorMessages: errorMessagesFor(),
+      httpErrors: httpErrorsFor(),
     })
   }
 
diff --git a/services/github/github-license.service.js b/services/github/github-license.service.js
index 360153f29e..9676335eac 100644
--- a/services/github/github-license.service.js
+++ b/services/github/github-license.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { renderLicenseBadge } from '../licenses.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   // Some repos do not have a license, in which case GitHub returns `{ license: null }`.
@@ -40,7 +40,7 @@ export default class GithubLicense extends GithubAuthV3Service {
     const { license: licenseObject } = await this._requestJson({
       schema,
       url: `/repos/${user}/${repo}`,
-      errorMessages: errorMessagesFor('repo not found'),
+      httpErrors: httpErrorsFor('repo not found'),
     })
 
     const license = licenseObject ? licenseObject.spdx_id : undefined
diff --git a/services/github/github-milestone-detail.service.js b/services/github/github-milestone-detail.service.js
index aef9845e3f..775a6e3278 100644
--- a/services/github/github-milestone-detail.service.js
+++ b/services/github/github-milestone-detail.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   open_issues: nonNegativeInteger,
@@ -85,7 +85,7 @@ export default class GithubMilestoneDetail extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}/milestones/${number}`,
       schema,
-      errorMessages: errorMessagesFor('repo or milestone not found'),
+      httpErrors: httpErrorsFor('repo or milestone not found'),
     })
   }
 
diff --git a/services/github/github-milestone.service.js b/services/github/github-milestone.service.js
index 5d16bc8d8f..81f2b5961e 100644
--- a/services/github/github-milestone.service.js
+++ b/services/github/github-milestone.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.array()
   .items(
@@ -70,7 +70,7 @@ export default class GithubMilestone extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}/milestones?state=${variant}`,
       schema,
-      errorMessages: errorMessagesFor('repo not found'),
+      httpErrors: httpErrorsFor('repo not found'),
     })
   }
 
diff --git a/services/github/github-pull-request-check-state.service.js b/services/github/github-pull-request-check-state.service.js
index 969c75d91b..4e2d2c9426 100644
--- a/services/github/github-pull-request-check-state.service.js
+++ b/services/github/github-pull-request-check-state.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import countBy from 'lodash.countby'
 import { GithubAuthV3Service } from './github-auth-service.js'
 import { fetchIssue } from './github-common-fetch.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   state: Joi.equal('failure', 'pending', 'success').required(),
@@ -92,7 +92,7 @@ export default class GithubPullRequestCheckState extends GithubAuthV3Service {
     const json = await this._requestJson({
       schema,
       url: `/repos/${user}/${repo}/commits/${ref}/status`,
-      errorMessages: errorMessagesFor('commit not found'),
+      httpErrors: httpErrorsFor('commit not found'),
     })
     const { state, stateCounts } = this.constructor.transform(json)
 
diff --git a/services/github/github-release-date.service.js b/services/github/github-release-date.service.js
index d6011a939f..6e382884ab 100644
--- a/services/github/github-release-date.service.js
+++ b/services/github/github-release-date.service.js
@@ -3,7 +3,7 @@ import Joi from 'joi'
 import { age } from '../color-formatters.js'
 import { formatDate } from '../text-formatters.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.alternatives(
   Joi.object({
@@ -86,7 +86,7 @@ export default class GithubReleaseDate extends GithubAuthV3Service {
     return this._requestJson({
       url,
       schema,
-      errorMessages: errorMessagesFor('no releases or repo not found'),
+      httpErrors: httpErrorsFor('no releases or repo not found'),
     })
   }
 
diff --git a/services/github/github-repo-size.service.js b/services/github/github-repo-size.service.js
index 774ddad426..7c78b13686 100644
--- a/services/github/github-repo-size.service.js
+++ b/services/github/github-repo-size.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import prettyBytes from 'pretty-bytes'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   size: nonNegativeInteger,
@@ -37,7 +37,7 @@ export default class GithubRepoSize extends GithubAuthV3Service {
     return this._requestJson({
       url: `/repos/${user}/${repo}`,
       schema,
-      errorMessages: errorMessagesFor(),
+      httpErrors: httpErrorsFor(),
     })
   }
 
diff --git a/services/github/github-search.service.js b/services/github/github-search.service.js
index 1770e5bca5..69ace99dd0 100644
--- a/services/github/github-search.service.js
+++ b/services/github/github-search.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { errorMessagesFor, documentation } from './github-helpers.js'
+import { httpErrorsFor, documentation } from './github-helpers.js'
 
 const schema = Joi.object({ total_count: nonNegativeInteger }).required()
 
@@ -49,7 +49,7 @@ export default class GithubSearch extends GithubAuthV3Service {
         },
       },
       schema,
-      errorMessages: errorMessagesFor('repo not found'),
+      httpErrors: httpErrorsFor('repo not found'),
     })
     return this.constructor.render({ query, totalCount })
   }
diff --git a/services/github/github-size.service.js b/services/github/github-size.service.js
index de2e2bc0a2..850a1e77d1 100644
--- a/services/github/github-size.service.js
+++ b/services/github/github-size.service.js
@@ -3,7 +3,7 @@ import prettyBytes from 'pretty-bytes'
 import { nonNegativeInteger } from '../validators.js'
 import { NotFound } from '../index.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const queryParamSchema = Joi.object({
   branch: Joi.string(),
@@ -65,13 +65,13 @@ export default class GithubSize extends GithubAuthV3Service {
       return this._requestJson({
         url: `/repos/${user}/${repo}/contents/${path}?ref=${branch}`,
         schema,
-        errorMessages: errorMessagesFor('repo, branch or file not found'),
+        httpErrors: httpErrorsFor('repo, branch or file not found'),
       })
     } else {
       return this._requestJson({
         url: `/repos/${user}/${repo}/contents/${path}`,
         schema,
-        errorMessages: errorMessagesFor('repo or file not found'),
+        httpErrors: httpErrorsFor('repo or file not found'),
       })
     }
   }
diff --git a/services/github/github-stars.service.js b/services/github/github-stars.service.js
index c790da3f23..77589e502e 100644
--- a/services/github/github-stars.service.js
+++ b/services/github/github-stars.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   stargazers_count: nonNegativeInteger,
@@ -56,7 +56,7 @@ export default class GithubStars extends GithubAuthV3Service {
     const { stargazers_count: stars } = await this._requestJson({
       url: `/repos/${user}/${repo}`,
       schema,
-      errorMessages: errorMessagesFor(),
+      httpErrors: httpErrorsFor(),
     })
     return this.constructor.render({ user, repo, stars })
   }
diff --git a/services/github/github-watchers.service.js b/services/github/github-watchers.service.js
index 088757ff93..ceb6f1893f 100644
--- a/services/github/github-watchers.service.js
+++ b/services/github/github-watchers.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV3Service } from './github-auth-service.js'
-import { documentation, errorMessagesFor } from './github-helpers.js'
+import { documentation, httpErrorsFor } from './github-helpers.js'
 
 const schema = Joi.object({
   subscribers_count: nonNegativeInteger,
@@ -55,7 +55,7 @@ export default class GithubWatchers extends GithubAuthV3Service {
     const { subscribers_count: watchers } = await this._requestJson({
       url: `/repos/${user}/${repo}`,
       schema,
-      errorMessages: errorMessagesFor(),
+      httpErrors: httpErrorsFor(),
     })
     return this.constructor.render({ user, repo, watchers })
   }
diff --git a/services/gitlab/gitlab-base.js b/services/gitlab/gitlab-base.js
index ecf1775e7d..76d0f6b8a4 100644
--- a/services/gitlab/gitlab-base.js
+++ b/services/gitlab/gitlab-base.js
@@ -6,13 +6,13 @@ export default class GitLabBase extends BaseJsonService {
     serviceKey: 'gitlab',
   }
 
-  async fetch({ url, options, schema, errorMessages }) {
+  async fetch({ url, options, schema, httpErrors }) {
     return this._requestJson(
       this.authHelper.withBearerAuthHeader({
         schema,
         url,
         options,
-        errorMessages,
+        httpErrors,
       })
     )
   }
@@ -34,7 +34,7 @@ export default class GitLabBase extends BaseJsonService {
     url,
     options,
     schema,
-    errorMessages,
+    httpErrors,
     firstPageOnly = false,
   }) {
     const requestParams = {
@@ -44,7 +44,7 @@ export default class GitLabBase extends BaseJsonService {
         searchParams: { per_page: 100 },
         ...options,
       },
-      errorMessages,
+      httpErrors,
     }
 
     const {
diff --git a/services/gitlab/gitlab-contributors.service.js b/services/gitlab/gitlab-contributors.service.js
index 5b8bb08dba..e93a8ce034 100644
--- a/services/gitlab/gitlab-contributors.service.js
+++ b/services/gitlab/gitlab-contributors.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { renderContributorBadge } from '../contributor-count.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({ 'x-total': nonNegativeInteger }).required()
@@ -52,7 +52,7 @@ export default class GitlabContributors extends GitLabBase {
           project
         )}/repository/contributors`,
         options: { searchParams: { page: '1', per_page: '1' } },
-        errorMessages: errorMessagesFor('project not found'),
+        httpErrors: httpErrorsFor('project not found'),
       })
     )
     const data = this.constructor._validate(res.headers, schema)
diff --git a/services/gitlab/gitlab-forks.service.js b/services/gitlab/gitlab-forks.service.js
index 29b31b290c..fa15704982 100644
--- a/services/gitlab/gitlab-forks.service.js
+++ b/services/gitlab/gitlab-forks.service.js
@@ -61,7 +61,7 @@ export default class GitlabForks extends GitLabBase {
     return super.fetch({
       schema,
       url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}`,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/gitlab/gitlab-helper.js b/services/gitlab/gitlab-helper.js
index d1f24ebba1..0580ea5840 100644
--- a/services/gitlab/gitlab-helper.js
+++ b/services/gitlab/gitlab-helper.js
@@ -9,11 +9,11 @@ const documentation = `
 </p>
 `
 
-function errorMessagesFor(notFoundMessage = 'project not found') {
+function httpErrorsFor(notFoundMessage = 'project not found') {
   return {
     401: notFoundMessage,
     404: notFoundMessage,
   }
 }
 
-export { documentation, errorMessagesFor }
+export { documentation, httpErrorsFor }
diff --git a/services/gitlab/gitlab-issues.service.js b/services/gitlab/gitlab-issues.service.js
index ad86069f95..3f88d66519 100644
--- a/services/gitlab/gitlab-issues.service.js
+++ b/services/gitlab/gitlab-issues.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({
@@ -237,7 +237,7 @@ export default class GitlabIssues extends GitLabBase {
         project
       )}/issues_statistics`,
       options: labels ? { searchParams: { labels } } : undefined,
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/gitlab/gitlab-languages-count.service.js b/services/gitlab/gitlab-languages-count.service.js
index 60e6588e7a..4d08f13b67 100644
--- a/services/gitlab/gitlab-languages-count.service.js
+++ b/services/gitlab/gitlab-languages-count.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { optionalUrl } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 /*
@@ -54,7 +54,7 @@ export default class GitlabLanguageCount extends GitLabBase {
       url: `${baseUrl}/api/v4/projects/${encodeURIComponent(
         project
       )}/languages`,
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/gitlab/gitlab-last-commit.service.js b/services/gitlab/gitlab-last-commit.service.js
index b2342d669b..c69050dfe4 100644
--- a/services/gitlab/gitlab-last-commit.service.js
+++ b/services/gitlab/gitlab-last-commit.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { optionalUrl } from '../validators.js'
 import { formatDate } from '../text-formatters.js'
 import { age as ageColor } from '../color-formatters.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array()
@@ -65,7 +65,7 @@ export default class GitlabLastCommit extends GitLabBase {
       )}/repository/commits`,
       options: { searchParams: { ref_name: ref } },
       schema,
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/gitlab/gitlab-license.service.js b/services/gitlab/gitlab-license.service.js
index 254696121c..131173ba8a 100644
--- a/services/gitlab/gitlab-license.service.js
+++ b/services/gitlab/gitlab-license.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { optionalUrl } from '../validators.js'
 import { renderLicenseBadge } from '../licenses.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({
@@ -67,7 +67,7 @@ export default class GitlabLicense extends GitLabBase {
       schema,
       url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}`,
       options: { searchParams: { license: '1' } },
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/gitlab/gitlab-merge-requests.service.js b/services/gitlab/gitlab-merge-requests.service.js
index bc7f7b88a1..5db63bcb6e 100644
--- a/services/gitlab/gitlab-merge-requests.service.js
+++ b/services/gitlab/gitlab-merge-requests.service.js
@@ -1,7 +1,7 @@
 import Joi from 'joi'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 // The total number of MR is in the `x-total` field in the headers.
@@ -314,7 +314,7 @@ export default class GitlabMergeRequests extends GitLabBase {
             labels,
           },
         },
-        errorMessages: errorMessagesFor('project not found'),
+        httpErrors: httpErrorsFor('project not found'),
       })
     )
     return this.constructor._validate(res.headers, schema)
diff --git a/services/gitlab/gitlab-pipeline-coverage.service.js b/services/gitlab/gitlab-pipeline-coverage.service.js
index 3267e8e4eb..e0a332907b 100644
--- a/services/gitlab/gitlab-pipeline-coverage.service.js
+++ b/services/gitlab/gitlab-pipeline-coverage.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { coveragePercentage } from '../color-formatters.js'
 import { optionalUrl } from '../validators.js'
 import { BaseSvgScrapingService, NotFound } from '../index.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 
 const schema = Joi.object({
   message: Joi.string()
@@ -98,11 +98,11 @@ export default class GitlabPipelineCoverage extends BaseSvgScrapingService {
     const url = `${baseUrl}/${decodeURIComponent(
       project
     )}/badges/${branch}/coverage.svg${jobName}`
-    const errorMessages = errorMessagesFor('project not found')
+    const httpErrors = httpErrorsFor('project not found')
     return this._requestSvg({
       schema,
       url,
-      errorMessages,
+      httpErrors,
     })
   }
 
diff --git a/services/gitlab/gitlab-pipeline-status.service.js b/services/gitlab/gitlab-pipeline-status.service.js
index dbd0cdbcd1..49246f861d 100644
--- a/services/gitlab/gitlab-pipeline-status.service.js
+++ b/services/gitlab/gitlab-pipeline-status.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
 import { optionalUrl } from '../validators.js'
 import { BaseSvgScrapingService, NotFound, redirector } from '../index.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 
 const badgeSchema = Joi.object({
   message: Joi.alternatives()
@@ -73,7 +73,7 @@ class GitlabPipelineStatus extends BaseSvgScrapingService {
       url: `${baseUrl}/${decodeURIComponent(
         project
       )}/badges/${branch}/pipeline.svg`,
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/gitlab/gitlab-release.service.js b/services/gitlab/gitlab-release.service.js
index 51f3c37291..28e3872b0c 100644
--- a/services/gitlab/gitlab-release.service.js
+++ b/services/gitlab/gitlab-release.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { optionalUrl } from '../validators.js'
 import { latest, renderVersionBadge } from '../version.js'
 import { NotFound } from '../index.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array().items(
@@ -98,7 +98,7 @@ export default class GitLabRelease extends GitLabBase {
     return this.fetchPaginatedArrayData({
       schema,
       url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}/releases`,
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
       options: {
         searchParams: { order_by: orderBy },
       },
diff --git a/services/gitlab/gitlab-stars.service.js b/services/gitlab/gitlab-stars.service.js
index a440c814f3..695c0db391 100644
--- a/services/gitlab/gitlab-stars.service.js
+++ b/services/gitlab/gitlab-stars.service.js
@@ -58,7 +58,7 @@ export default class GitlabStars extends GitLabBase {
     return super.fetch({
       schema,
       url: `${baseUrl}/api/v4/projects/${encodeURIComponent(project)}`,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/gitlab/gitlab-tag.service.js b/services/gitlab/gitlab-tag.service.js
index 534d371972..10f7bd62ed 100644
--- a/services/gitlab/gitlab-tag.service.js
+++ b/services/gitlab/gitlab-tag.service.js
@@ -4,7 +4,7 @@ import { optionalUrl } from '../validators.js'
 import { latest } from '../version.js'
 import { addv } from '../text-formatters.js'
 import { NotFound } from '../index.js'
-import { documentation, errorMessagesFor } from './gitlab-helper.js'
+import { documentation, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array().items(
@@ -92,7 +92,7 @@ export default class GitlabTag extends GitLabBase {
         project
       )}/repository/tags`,
       options: { searchParams: { order_by: 'updated' } },
-      errorMessages: errorMessagesFor('project not found'),
+      httpErrors: httpErrorsFor('project not found'),
     })
   }
 
diff --git a/services/hackernews/hackernews-user-karma.service.js b/services/hackernews/hackernews-user-karma.service.js
index 6ff5638ba7..8d37f841f3 100644
--- a/services/hackernews/hackernews-user-karma.service.js
+++ b/services/hackernews/hackernews-user-karma.service.js
@@ -44,7 +44,7 @@ export default class HackerNewsUserKarma extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://hacker-news.firebaseio.com/v0/user/${id}.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'user not found',
       },
     })
diff --git a/services/homebrew/homebrew-downloads.service.js b/services/homebrew/homebrew-downloads.service.js
index 20e19b23e4..e7989e48b3 100644
--- a/services/homebrew/homebrew-downloads.service.js
+++ b/services/homebrew/homebrew-downloads.service.js
@@ -53,7 +53,7 @@ export default class HomebrewDownloads extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://formulae.brew.sh/api/formula/${formula}.json`,
-      errorMessages: { 404: 'formula not found' },
+      httpErrors: { 404: 'formula not found' },
     })
   }
 
diff --git a/services/jenkins/jenkins-base.js b/services/jenkins/jenkins-base.js
index 35398c0301..2a52b2d476 100644
--- a/services/jenkins/jenkins-base.js
+++ b/services/jenkins/jenkins-base.js
@@ -11,14 +11,14 @@ export default class JenkinsBase extends BaseJsonService {
     url,
     schema,
     searchParams,
-    errorMessages = { 404: 'instance or job not found' },
+    httpErrors = { 404: 'instance or job not found' },
   }) {
     return this._requestJson(
       this.authHelper.withBasicAuth({
         url,
         options: { searchParams },
         schema,
-        errorMessages,
+        httpErrors,
       })
     )
   }
diff --git a/services/jenkins/jenkins-coverage.service.js b/services/jenkins/jenkins-coverage.service.js
index 9727fdca4b..c072abe7c2 100644
--- a/services/jenkins/jenkins-coverage.service.js
+++ b/services/jenkins/jenkins-coverage.service.js
@@ -138,7 +138,7 @@ export default class JenkinsCoverage extends JenkinsBase {
       url: buildUrl({ jobUrl, plugin: pluginSpecificPath }),
       schema,
       searchParams: buildTreeParamQueryString(treeQueryParam),
-      errorMessages: {
+      httpErrors: {
         404: 'job or coverage not found',
       },
     })
diff --git a/services/jenkins/jenkins-plugin-installs.service.js b/services/jenkins/jenkins-plugin-installs.service.js
index 456d2071b6..ce51df1718 100644
--- a/services/jenkins/jenkins-plugin-installs.service.js
+++ b/services/jenkins/jenkins-plugin-installs.service.js
@@ -63,7 +63,7 @@ export default class JenkinsPluginInstalls extends BaseJsonService {
     return this._requestJson({
       url: `https://stats.jenkins.io/plugin-installation-trend/${plugin}.stats.json`,
       schema: version ? schemaInstallationsPerVersion : schemaInstallations,
-      errorMessages: {
+      httpErrors: {
         404: 'plugin not found',
       },
     })
diff --git a/services/jetbrains/jetbrains-base.js b/services/jetbrains/jetbrains-base.js
index ea8bc5d5c1..fe3c497c47 100644
--- a/services/jetbrains/jetbrains-base.js
+++ b/services/jetbrains/jetbrains-base.js
@@ -54,7 +54,7 @@ export default class JetbrainsBase extends BaseXmlService {
     return super._validate(data, schema)
   }
 
-  async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
+  async _requestJson({ schema, url, options = {}, httpErrors = {} }) {
     const mergedOptions = {
       ...{ headers: { Accept: 'application/json' } },
       ...options,
@@ -62,7 +62,7 @@ export default class JetbrainsBase extends BaseXmlService {
     const { buffer } = await this._request({
       url,
       options: mergedOptions,
-      errorMessages,
+      httpErrors,
     })
     const json = this._parseJson(buffer)
     return this.constructor._validateJson(json, schema)
diff --git a/services/jetbrains/jetbrains-downloads.service.js b/services/jetbrains/jetbrains-downloads.service.js
index fc91c78ce3..7db2403e28 100644
--- a/services/jetbrains/jetbrains-downloads.service.js
+++ b/services/jetbrains/jetbrains-downloads.service.js
@@ -56,7 +56,7 @@ export default class JetbrainsDownloads extends JetbrainsBase {
         url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
           pluginId
         )}`,
-        errorMessages: { 400: 'not found' },
+        httpErrors: { 400: 'not found' },
       })
       downloads = jetbrainsPluginData.downloads
     }
diff --git a/services/jetbrains/jetbrains-rating.service.js b/services/jetbrains/jetbrains-rating.service.js
index 81fa3fb61b..5c3fd2d757 100644
--- a/services/jetbrains/jetbrains-rating.service.js
+++ b/services/jetbrains/jetbrains-rating.service.js
@@ -92,7 +92,7 @@ export default class JetbrainsRating extends JetbrainsBase {
         url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
           pluginId
         )}/rating`,
-        errorMessages: { 400: 'not found' },
+        httpErrors: { 400: 'not found' },
       })
 
       let voteSum = 0
diff --git a/services/jetbrains/jetbrains-version.service.js b/services/jetbrains/jetbrains-version.service.js
index 2b7d8dd3b2..dad18b10a9 100644
--- a/services/jetbrains/jetbrains-version.service.js
+++ b/services/jetbrains/jetbrains-version.service.js
@@ -67,7 +67,7 @@ export default class JetbrainsVersion extends JetbrainsBase {
         url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
           pluginId
         )}/updates`,
-        errorMessages: { 400: 'not found' },
+        httpErrors: { 400: 'not found' },
       })
       version = jetbrainsPluginData[0].version
     }
diff --git a/services/jira/jira-issue.service.js b/services/jira/jira-issue.service.js
index 35fafa60a3..609c81dd1d 100644
--- a/services/jira/jira-issue.service.js
+++ b/services/jira/jira-issue.service.js
@@ -75,7 +75,7 @@ export default class JiraIssue extends BaseJsonService {
       this.authHelper.withBasicAuth({
         schema,
         url: `${baseUrl}/rest/api/2/issue/${encodeURIComponent(issueKey)}`,
-        errorMessages: { 404: 'issue not found' },
+        httpErrors: { 404: 'issue not found' },
       })
     )
 
diff --git a/services/jira/jira-sprint.service.js b/services/jira/jira-sprint.service.js
index eaf6b0bcc3..2ab4d31a67 100644
--- a/services/jira/jira-sprint.service.js
+++ b/services/jira/jira-sprint.service.js
@@ -92,7 +92,7 @@ export default class JiraSprint extends BaseJsonService {
             maxResults: 500,
           },
         },
-        errorMessages: {
+        httpErrors: {
           400: 'sprint not found',
           404: 'sprint not found',
         },
diff --git a/services/jitpack/jitpack-version.service.js b/services/jitpack/jitpack-version.service.js
index 8420ee58c0..7b088dbffe 100644
--- a/services/jitpack/jitpack-version.service.js
+++ b/services/jitpack/jitpack-version.service.js
@@ -37,7 +37,7 @@ export default class JitPackVersion extends BaseJsonService {
     return this._requestJson({
       schema,
       url,
-      errorMessages: { 401: 'project not found or private' },
+      httpErrors: { 401: 'project not found or private' },
     })
   }
 
diff --git a/services/librariesio/librariesio-base.js b/services/librariesio/librariesio-base.js
index 26183df917..850db1e63b 100644
--- a/services/librariesio/librariesio-base.js
+++ b/services/librariesio/librariesio-base.js
@@ -26,7 +26,7 @@ export default class LibrariesIoBase extends BaseJsonService {
       url: `/${encodeURIComponent(platform)}/${
         scope ? encodeURIComponent(`${scope}/`) : ''
       }${encodeURIComponent(packageName)}`,
-      errorMessages: { 404: 'package not found' },
+      httpErrors: { 404: 'package not found' },
     })
   }
 }
diff --git a/services/librariesio/librariesio-dependencies.service.js b/services/librariesio/librariesio-dependencies.service.js
index 6c8190534d..5e12a48033 100644
--- a/services/librariesio/librariesio-dependencies.service.js
+++ b/services/librariesio/librariesio-dependencies.service.js
@@ -92,7 +92,7 @@ class LibrariesIoProjectDependencies extends LibrariesIoBase {
     const json = await this._requestJson({
       url,
       schema,
-      errorMessages: { 404: 'package or version not found' },
+      httpErrors: { 404: 'package or version not found' },
     })
     const { deprecatedCount, outdatedCount } = transform(json)
     return renderDependenciesBadge({ deprecatedCount, outdatedCount })
@@ -127,7 +127,7 @@ class LibrariesIoRepoDependencies extends LibrariesIoBase {
     const json = await this._requestJson({
       url,
       schema,
-      errorMessages: {
+      httpErrors: {
         404: 'repo not found',
       },
     })
diff --git a/services/localizely/localizely.service.js b/services/localizely/localizely.service.js
index 47c301453b..1dc13f9478 100644
--- a/services/localizely/localizely.service.js
+++ b/services/localizely/localizely.service.js
@@ -111,7 +111,7 @@ export default class Localizely extends BaseJsonService {
         searchParams: { branch },
         headers: { 'X-Api-Token': apiToken },
       },
-      errorMessages: {
+      httpErrors: {
         403: 'not authorized for project',
       },
     })
diff --git a/services/luarocks/luarocks.service.js b/services/luarocks/luarocks.service.js
index 2ffb68616a..49d75fbe4b 100644
--- a/services/luarocks/luarocks.service.js
+++ b/services/luarocks/luarocks.service.js
@@ -60,7 +60,7 @@ export default class Luarocks extends BaseJsonService {
         user
       )}/manifest.json`,
       schema,
-      errorMessages: {
+      httpErrors: {
         404: 'user not found',
       },
     })
diff --git a/services/matrix/matrix.service.js b/services/matrix/matrix.service.js
index 50c28ad847..8371388e02 100644
--- a/services/matrix/matrix.service.js
+++ b/services/matrix/matrix.service.js
@@ -116,7 +116,7 @@ export default class Matrix extends BaseJsonService {
           auth: { type: 'm.login.dummy' },
         }),
       },
-      errorMessages: {
+      httpErrors: {
         401: 'auth failed',
         403: 'guests not allowed',
       },
@@ -134,7 +134,7 @@ export default class Matrix extends BaseJsonService {
           access_token: accessToken,
         },
       },
-      errorMessages: {
+      httpErrors: {
         401: 'bad auth token',
         404: 'room not found',
       },
@@ -172,7 +172,7 @@ export default class Matrix extends BaseJsonService {
           access_token: accessToken,
         },
       },
-      errorMessages: {
+      httpErrors: {
         400: 'unknown request',
         401: 'bad auth token',
         403: 'room not world readable or is invalid',
diff --git a/services/nexus/nexus.service.js b/services/nexus/nexus.service.js
index 8f13df2b46..b2b166a719 100644
--- a/services/nexus/nexus.service.js
+++ b/services/nexus/nexus.service.js
@@ -220,7 +220,7 @@ export default class Nexus extends BaseJsonService {
         schema,
         url,
         options: { searchParams },
-        errorMessages: {
+        httpErrors: {
           404: 'artifact not found',
         },
       })
@@ -261,7 +261,7 @@ export default class Nexus extends BaseJsonService {
         schema: nexus3SearchApiSchema,
         url,
         options: { searchParams },
-        errorMessages: {
+        httpErrors: {
           404: 'artifact not found',
         },
       })
diff --git a/services/npm/npm-base.js b/services/npm/npm-base.js
index 7f8ed6cbc2..b4dc250d62 100644
--- a/services/npm/npm-base.js
+++ b/services/npm/npm-base.js
@@ -117,7 +117,7 @@ export default class NpmBase extends BaseJsonService {
       // We don't validate here because we need to pluck the desired subkey first.
       schema: Joi.any(),
       url,
-      errorMessages: { 404: 'package not found' },
+      httpErrors: { 404: 'package not found' },
     })
 
     let packageData
diff --git a/services/npm/npm-downloads.service.js b/services/npm/npm-downloads.service.js
index 9745b5db53..ea6ea7eefc 100644
--- a/services/npm/npm-downloads.service.js
+++ b/services/npm/npm-downloads.service.js
@@ -77,7 +77,7 @@ export default class NpmDownloads extends BaseJsonService {
     const json = await this._requestJson({
       schema,
       url: `https://api.npmjs.org/downloads/${query}/${slug}`,
-      errorMessages: { 404: 'package not found or too new' },
+      httpErrors: { 404: 'package not found or too new' },
     })
 
     const downloadCount = transform(json)
diff --git a/services/npm/npm-version.service.js b/services/npm/npm-version.service.js
index 20caf3e998..80fd3c9e09 100644
--- a/services/npm/npm-version.service.js
+++ b/services/npm/npm-version.service.js
@@ -80,7 +80,7 @@ export default class NpmVersion extends NpmBase {
     const packageData = await this._requestJson({
       schema,
       url: `${registryUrl}/-/package/${slug}/dist-tags`,
-      errorMessages: { 404: 'package not found' },
+      httpErrors: { 404: 'package not found' },
     })
 
     if (tag && !(tag in packageData)) {
diff --git a/services/npms-io/npms-io-score.service.js b/services/npms-io/npms-io-score.service.js
index b0f4891089..6de07aa4dd 100644
--- a/services/npms-io/npms-io-score.service.js
+++ b/services/npms-io/npms-io-score.service.js
@@ -77,7 +77,7 @@ export default class NpmsIOScore extends BaseJsonService {
     const json = await this._requestJson({
       schema: responseSchema,
       url,
-      errorMessages: { 404: 'package not found or too new' },
+      httpErrors: { 404: 'package not found or too new' },
     })
 
     const scoreType = type.slice(0, -6)
diff --git a/services/open-vsx/open-vsx-base.js b/services/open-vsx/open-vsx-base.js
index d17a1e66a8..b7a7965660 100644
--- a/services/open-vsx/open-vsx-base.js
+++ b/services/open-vsx/open-vsx-base.js
@@ -34,7 +34,7 @@ export default class OpenVSXBase extends BaseJsonService {
       url: `https://open-vsx.org/api/${namespace}/${extension}/${
         version || ''
       }`,
-      errorMessages: {
+      httpErrors: {
         400: 'invalid extension id',
         404: 'extension not found',
       },
diff --git a/services/opencollective/opencollective-base.js b/services/opencollective/opencollective-base.js
index 9019820997..203cfabf03 100644
--- a/services/opencollective/opencollective-base.js
+++ b/services/opencollective/opencollective-base.js
@@ -43,7 +43,7 @@ export default class OpencollectiveBase extends BaseJsonService {
       schema: collectiveDetailsSchema,
       // https://developer.opencollective.com/#/api/collectives?id=get-info
       url: `https://opencollective.com/${collective}.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'collective not found',
       },
     })
@@ -66,7 +66,7 @@ export default class OpencollectiveBase extends BaseJsonService {
       url: `https://opencollective.com/${collective}/members/${
         userType || 'all'
       }.json${tierId ? `?TierId=${tierId}` : ''}`,
-      errorMessages: {
+      httpErrors: {
         404: 'collective not found',
       },
     })
diff --git a/services/opm/opm-version.service.js b/services/opm/opm-version.service.js
index b0c8b5e7fe..e053588628 100644
--- a/services/opm/opm-version.service.js
+++ b/services/opm/opm-version.service.js
@@ -34,7 +34,7 @@ export default class OpmVersion extends BaseService {
           name: moduleName,
         },
       },
-      errorMessages: {
+      httpErrors: {
         404: 'module not found',
       },
     })
diff --git a/services/ossf-scorecard/ossf-scorecard.service.js b/services/ossf-scorecard/ossf-scorecard.service.js
index a7489bcd67..9d17322943 100644
--- a/services/ossf-scorecard/ossf-scorecard.service.js
+++ b/services/ossf-scorecard/ossf-scorecard.service.js
@@ -41,7 +41,7 @@ export default class OSSFScorecard extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://api.securityscorecards.dev/projects/${host}/${orgName}/${repoName}`,
-      errorMessages: {
+      httpErrors: {
         404: 'invalid repo path',
       },
     })
diff --git a/services/piwheels/piwheels-version.service.js b/services/piwheels/piwheels-version.service.js
index 51be963d6e..b83e341c03 100644
--- a/services/piwheels/piwheels-version.service.js
+++ b/services/piwheels/piwheels-version.service.js
@@ -55,7 +55,7 @@ export default class PiWheelsVersion extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://www.piwheels.org/project/${wheel}/json/`,
-      errorMessages: { 404: 'package not found' },
+      httpErrors: { 404: 'package not found' },
     })
   }
 
diff --git a/services/pypi/pypi-base.js b/services/pypi/pypi-base.js
index 012af06f49..48f7e43f83 100644
--- a/services/pypi/pypi-base.js
+++ b/services/pypi/pypi-base.js
@@ -30,7 +30,7 @@ export default class PypiBase extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://pypi.org/pypi/${egg}/json`,
-      errorMessages: { 404: 'package or version not found' },
+      httpErrors: { 404: 'package or version not found' },
     })
   }
 }
diff --git a/services/pypi/pypi-downloads.service.js b/services/pypi/pypi-downloads.service.js
index abc0647f30..d6449bcdcd 100644
--- a/services/pypi/pypi-downloads.service.js
+++ b/services/pypi/pypi-downloads.service.js
@@ -59,7 +59,7 @@ export default class PypiDownloads extends BaseJsonService {
     return this._requestJson({
       url: `https://pypistats.org/api/packages/${packageName.toLowerCase()}/recent`,
       schema,
-      errorMessages: { 404: 'package not found' },
+      httpErrors: { 404: 'package not found' },
     })
   }
 
diff --git a/services/reddit/subreddit-subscribers.service.js b/services/reddit/subreddit-subscribers.service.js
index 2d0ce5c53d..d162e6fd0c 100644
--- a/services/reddit/subreddit-subscribers.service.js
+++ b/services/reddit/subreddit-subscribers.service.js
@@ -48,7 +48,7 @@ export default class RedditSubredditSubscribers extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://www.reddit.com/r/${subreddit}/about.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'subreddit not found',
         403: 'subreddit is private',
       },
diff --git a/services/reddit/user-karma.service.js b/services/reddit/user-karma.service.js
index b53edfc68a..fe873aa046 100644
--- a/services/reddit/user-karma.service.js
+++ b/services/reddit/user-karma.service.js
@@ -53,7 +53,7 @@ export default class RedditUserKarma extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://www.reddit.com/u/${user}/about.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'user not found',
       },
     })
diff --git a/services/reuse/reuse-compliance.service.js b/services/reuse/reuse-compliance.service.js
index 93933f5f90..2473572d37 100644
--- a/services/reuse/reuse-compliance.service.js
+++ b/services/reuse/reuse-compliance.service.js
@@ -41,7 +41,7 @@ export default class Reuse extends BaseJsonService {
     return await this._requestJson({
       schema: responseSchema,
       url: `https://api.reuse.software/status/${remote}`,
-      errorMessages: {
+      httpErrors: {
         400: 'Not a Git repository',
       },
     })
diff --git a/services/scrutinizer/scrutinizer-base.js b/services/scrutinizer/scrutinizer-base.js
index 918a9b6dec..90f4fe9062 100644
--- a/services/scrutinizer/scrutinizer-base.js
+++ b/services/scrutinizer/scrutinizer-base.js
@@ -6,7 +6,7 @@ export default class ScrutinizerBase extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://scrutinizer-ci.com/api/repositories/${vcs}/${slug}`,
-      errorMessages: {
+      httpErrors: {
         401: 'not authorized to access project',
         404: 'project not found',
       },
diff --git a/services/snyk/snyk-vulnerability-base.js b/services/snyk/snyk-vulnerability-base.js
index 39a5fd33f9..ee25d1dd44 100644
--- a/services/snyk/snyk-vulnerability-base.js
+++ b/services/snyk/snyk-vulnerability-base.js
@@ -25,14 +25,14 @@ export default class SnykVulnerabilityBase extends BaseSvgScrapingService {
     }
   }
 
-  async fetch({ url, searchParams, errorMessages }) {
+  async fetch({ url, searchParams, httpErrors }) {
     const { message: vulnerabilities } = await this._requestSvg({
       url,
       schema,
       options: {
         searchParams,
       },
-      errorMessages,
+      httpErrors,
     })
 
     return { vulnerabilities }
diff --git a/services/snyk/snyk-vulnerability-github.service.js b/services/snyk/snyk-vulnerability-github.service.js
index 535dc31114..7611e4ffd4 100644
--- a/services/snyk/snyk-vulnerability-github.service.js
+++ b/services/snyk/snyk-vulnerability-github.service.js
@@ -39,7 +39,7 @@ export default class SnykVulnerabilityGitHub extends SynkVulnerabilityBase {
     const { vulnerabilities } = await this.fetch({
       url,
       searchParams,
-      errorMessages: {
+      httpErrors: {
         404: 'repo or manifest not found',
       },
     })
diff --git a/services/snyk/snyk-vulnerability-npm.service.js b/services/snyk/snyk-vulnerability-npm.service.js
index 4e5f99db3e..5043608400 100644
--- a/services/snyk/snyk-vulnerability-npm.service.js
+++ b/services/snyk/snyk-vulnerability-npm.service.js
@@ -43,7 +43,7 @@ export default class SnykVulnerabilityNpm extends SynkVulnerabilityBase {
         // Snyk returns an HTTP 200 with an HTML page when the specified
         // npm package is not found that contains the text 404.
         // Including this in case Snyk starts returning a 404 response code instead.
-        errorMessages: {
+        httpErrors: {
           404: 'npm package is invalid or does not exist',
         },
       })
diff --git a/services/sonar/sonar-base.js b/services/sonar/sonar-base.js
index 2e4fd807ca..aaa29d3346 100644
--- a/services/sonar/sonar-base.js
+++ b/services/sonar/sonar-base.js
@@ -84,7 +84,7 @@ export default class SonarBase extends BaseJsonService {
         schema,
         url,
         options: { searchParams },
-        errorMessages: {
+        httpErrors: {
           404: 'component or metric not found, or legacy API not supported',
         },
       })
diff --git a/services/sourceforge/sourceforge-base.js b/services/sourceforge/sourceforge-base.js
index c8e84ed0df..e1c973a8cf 100644
--- a/services/sourceforge/sourceforge-base.js
+++ b/services/sourceforge/sourceforge-base.js
@@ -5,7 +5,7 @@ export default class BaseSourceForgeService extends BaseJsonService {
     return this._requestJson({
       url: `https://sourceforge.net/rest/p/${project}/`,
       schema,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/sourceforge/sourceforge-commit-count.service.js b/services/sourceforge/sourceforge-commit-count.service.js
index 581f4384c2..d7c9f89275 100644
--- a/services/sourceforge/sourceforge-commit-count.service.js
+++ b/services/sourceforge/sourceforge-commit-count.service.js
@@ -39,7 +39,7 @@ export default class SourceforgeCommitCount extends BaseJsonService {
     return this._requestJson({
       url: `https://sourceforge.net/rest/p/${project}/git`,
       schema,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/sourceforge/sourceforge-downloads.service.js b/services/sourceforge/sourceforge-downloads.service.js
index f382b5c1e2..134c52f20c 100644
--- a/services/sourceforge/sourceforge-downloads.service.js
+++ b/services/sourceforge/sourceforge-downloads.service.js
@@ -91,7 +91,7 @@ export default class SourceforgeDownloads extends BaseJsonService {
       schema,
       url,
       options,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/sourceforge/sourceforge-last-commit.service.js b/services/sourceforge/sourceforge-last-commit.service.js
index 9e6acfb38f..5b4315af6f 100644
--- a/services/sourceforge/sourceforge-last-commit.service.js
+++ b/services/sourceforge/sourceforge-last-commit.service.js
@@ -46,7 +46,7 @@ export default class SourceforgeLastCommit extends BaseJsonService {
     return this._requestJson({
       url: `https://sourceforge.net/rest/p/${project}/git/commits`,
       schema,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/sourceforge/sourceforge-open-tickets.service.js b/services/sourceforge/sourceforge-open-tickets.service.js
index 7cf956105b..d6fa6b2c26 100644
--- a/services/sourceforge/sourceforge-open-tickets.service.js
+++ b/services/sourceforge/sourceforge-open-tickets.service.js
@@ -43,7 +43,7 @@ export default class SourceforgeOpenTickets extends BaseJsonService {
     return this._requestJson({
       schema,
       url,
-      errorMessages: {
+      httpErrors: {
         404: 'project not found',
       },
     })
diff --git a/services/spack/spack.service.js b/services/spack/spack.service.js
index c3ad8e527f..a052431d74 100644
--- a/services/spack/spack.service.js
+++ b/services/spack/spack.service.js
@@ -32,7 +32,7 @@ export default class SpackVersion extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://packages.spack.io/data/packages/${packageName}.json`,
-      errorMessages: {
+      httpErrors: {
         404: 'package not found',
       },
     })
diff --git a/services/stackexchange/stackexchange-reputation.service.js b/services/stackexchange/stackexchange-reputation.service.js
index db2e3df40d..b75f61e39e 100644
--- a/services/stackexchange/stackexchange-reputation.service.js
+++ b/services/stackexchange/stackexchange-reputation.service.js
@@ -49,7 +49,7 @@ export default class StackExchangeReputation extends StackExchangeBase {
       schema: reputationSchema,
       options: { decompress: true, searchParams: { site: stackexchangesite } },
       url: `https://api.stackexchange.com/2.2/${path}`,
-      errorMessages: {
+      httpErrors: {
         400: 'invalid parameters',
       },
     })
diff --git a/services/steam/steam-base.js b/services/steam/steam-base.js
index 9ea5cae989..459c6d2a7a 100644
--- a/services/steam/steam-base.js
+++ b/services/steam/steam-base.js
@@ -45,7 +45,7 @@ class BaseSteamAPI extends BaseJsonService {
     return this._requestJson({
       url,
       schema,
-      errorMessages: {
+      httpErrors: {
         400: 'bad request',
       },
       options,
diff --git a/services/symfony/symfony-insight-base.js b/services/symfony/symfony-insight-base.js
index 8cace75a89..95318439af 100644
--- a/services/symfony/symfony-insight-base.js
+++ b/services/symfony/symfony-insight-base.js
@@ -62,7 +62,7 @@ class SymfonyInsightBase extends BaseXmlService {
         options: {
           headers: { Accept: 'application/vnd.com.sensiolabs.insight+xml' },
         },
-        errorMessages: {
+        httpErrors: {
           401: 'not authorized to access project',
           404: 'project not found',
         },
diff --git a/services/tas/tas-tests.service.js b/services/tas/tas-tests.service.js
index b347f8caaa..70836b0f7c 100644
--- a/services/tas/tas-tests.service.js
+++ b/services/tas/tas-tests.service.js
@@ -80,7 +80,7 @@ export default class TasBuildStatus extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://api.tas.lambdatest.com/repo/badge?git_provider=${provider}&org=${org}&repo=${repo}`,
-      errorMessages: {
+      httpErrors: {
         401: 'private project not supported',
         404: 'project not found',
       },
diff --git a/services/teamcity/teamcity-base.js b/services/teamcity/teamcity-base.js
index a3b447c72d..00b60da40b 100644
--- a/services/teamcity/teamcity-base.js
+++ b/services/teamcity/teamcity-base.js
@@ -7,7 +7,7 @@ export default class TeamCityBase extends BaseJsonService {
     serviceKey: 'teamcity',
   }
 
-  async fetch({ url, schema, searchParams = {}, errorMessages = {} }) {
+  async fetch({ url, schema, searchParams = {}, httpErrors = {} }) {
     // JetBrains API Auth Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-RESTAuthentication
     const options = { searchParams }
     if (!this.authHelper.isConfigured) {
@@ -19,7 +19,7 @@ export default class TeamCityBase extends BaseJsonService {
         url,
         schema,
         options,
-        errorMessages: { 404: 'build not found', ...errorMessages },
+        httpErrors: { 404: 'build not found', ...httpErrors },
       })
     )
   }
diff --git a/services/testspace/testspace-base.js b/services/testspace/testspace-base.js
index 5f74c556a0..556aaf2b7f 100644
--- a/services/testspace/testspace-base.js
+++ b/services/testspace/testspace-base.js
@@ -35,7 +35,7 @@ export default class TestspaceBase extends BaseJsonService {
     return this._requestJson({
       schema,
       url,
-      errorMessages: {
+      httpErrors: {
         403: 'org not found or not authorized',
         404: 'org, project, or space not found',
       },
diff --git a/services/tokei/tokei.service.js b/services/tokei/tokei.service.js
index dd01918b8d..2a6baeb8c5 100644
--- a/services/tokei/tokei.service.js
+++ b/services/tokei/tokei.service.js
@@ -65,7 +65,7 @@ export default class Tokei extends BaseJsonService {
     return this._requestJson({
       schema,
       url: `https://tokei.rs/b1/${provider}/${user}/${repo}`,
-      errorMessages: {
+      httpErrors: {
         400: 'repo not found',
       },
     })
diff --git a/services/twitch/twitch-base.js b/services/twitch/twitch-base.js
index 844157c3f1..3ca2b135a0 100644
--- a/services/twitch/twitch-base.js
+++ b/services/twitch/twitch-base.js
@@ -46,7 +46,7 @@ export default class TwitchBase extends BaseJsonService {
               grant_type: 'client_credentials',
             },
           },
-          errorMessages: {
+          httpErrors: {
             401: 'invalid token',
             404: 'node not found',
           },
diff --git a/services/ubuntu/ubuntu.service.js b/services/ubuntu/ubuntu.service.js
index a52d61b14b..fae6f52837 100644
--- a/services/ubuntu/ubuntu.service.js
+++ b/services/ubuntu/ubuntu.service.js
@@ -53,7 +53,7 @@ export default class Ubuntu extends BaseJsonService {
           ...seriesParam,
         },
       },
-      errorMessages: {
+      httpErrors: {
         400: 'series not found',
       },
     })
diff --git a/services/visual-studio-app-center/visual-studio-app-center-base.js b/services/visual-studio-app-center/visual-studio-app-center-base.js
index e90a6ee2f3..72952155d5 100644
--- a/services/visual-studio-app-center/visual-studio-app-center-base.js
+++ b/services/visual-studio-app-center/visual-studio-app-center-base.js
@@ -25,7 +25,7 @@ class BaseVisualStudioAppCenterService extends BaseJsonService {
           'X-API-Token': token,
         },
       },
-      errorMessages: {
+      httpErrors: {
         401: 'invalid token',
         403: 'project not found',
         404: 'project not found',
diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-base.js b/services/visual-studio-marketplace/visual-studio-marketplace-base.js
index 95acda7747..53456a91df 100644
--- a/services/visual-studio-marketplace/visual-studio-marketplace-base.js
+++ b/services/visual-studio-marketplace/visual-studio-marketplace-base.js
@@ -95,7 +95,7 @@ export default class VisualStudioMarketplaceBase extends BaseJsonService {
       schema: extensionQuerySchema,
       url,
       options,
-      errorMessages: {
+      httpErrors: {
         400: 'invalid extension id',
       },
     })
diff --git a/services/weblate/weblate-component-license.service.js b/services/weblate/weblate-component-license.service.js
index a24f5c3ab9..2a31fa8b5c 100644
--- a/services/weblate/weblate-component-license.service.js
+++ b/services/weblate/weblate-component-license.service.js
@@ -37,7 +37,7 @@ export default class WeblateComponentLicense extends WeblateBase {
     return super.fetch({
       schema,
       url: `${server}/api/components/${project}/${component}/`,
-      errorMessages: {
+      httpErrors: {
         403: 'access denied by remote server',
         404: 'component not found',
       },
diff --git a/services/weblate/weblate-entities.service.js b/services/weblate/weblate-entities.service.js
index f8db2d25ca..26f59b4774 100644
--- a/services/weblate/weblate-entities.service.js
+++ b/services/weblate/weblate-entities.service.js
@@ -36,7 +36,7 @@ export default class WeblateEntities extends WeblateBase {
     return super.fetch({
       schema,
       url: `${server}/api/${type}/`,
-      errorMessages: {
+      httpErrors: {
         403: 'access denied by remote server',
       },
     })
diff --git a/services/weblate/weblate-project-translated-percentage.service.js b/services/weblate/weblate-project-translated-percentage.service.js
index e8089f3b37..d9743b03c3 100644
--- a/services/weblate/weblate-project-translated-percentage.service.js
+++ b/services/weblate/weblate-project-translated-percentage.service.js
@@ -49,7 +49,7 @@ export default class WeblateProjectTranslatedPercentage extends WeblateBase {
     return super.fetch({
       schema,
       url: `${server}/api/projects/${project}/statistics/`,
-      errorMessages: {
+      httpErrors: {
         403: 'access denied by remote server',
         404: 'project not found',
       },
diff --git a/services/weblate/weblate-user-statistic.service.js b/services/weblate/weblate-user-statistic.service.js
index 639153bc68..6a4c3a89dc 100644
--- a/services/weblate/weblate-user-statistic.service.js
+++ b/services/weblate/weblate-user-statistic.service.js
@@ -49,7 +49,7 @@ export default class WeblateUserStatistic extends WeblateBase {
     return super.fetch({
       schema,
       url: `${server}/api/users/${user}/statistics/`,
-      errorMessages: {
+      httpErrors: {
         403: 'access denied by remote server',
         404: 'user not found',
       },
diff --git a/services/wheelmap/wheelmap.service.js b/services/wheelmap/wheelmap.service.js
index c6abf22d8c..98a43ef1d4 100644
--- a/services/wheelmap/wheelmap.service.js
+++ b/services/wheelmap/wheelmap.service.js
@@ -50,7 +50,7 @@ export default class Wheelmap extends BaseJsonService {
         {
           schema,
           url: `https://wheelmap.org/api/nodes/${nodeId}`,
-          errorMessages: {
+          httpErrors: {
             401: 'invalid token',
             404: 'node not found',
           },
-- 
GitLab