serverless/serverless · error · ServerlessError

API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED

API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED

Error message

V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}

What it means

Thrown by APIGatewayV1Wrapper.getCustomDomain when GetDomainNameCommand fails for any reason OTHER than a silent 404. The branch at line 108 suppresses only a 404 when silent=true; every other status (or a non-silent 404) bubbles up as this error. It is the read-path counterpart to creation failures and is used during deploy/teardown to decide whether a domain already exists.

Source

Thrown at packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js:109

  /**
   * Get Custom Domain Info
   * @param {DomainConfig} domain
   * @param {boolean} silent To issue an error or not. Not by default.
   * @returns {Promise<DomainInfo>}
   */
  async getCustomDomain(domain, silent = true) {
    // Make API call
    try {
      const domainInfo = await this.apiGateway.send(
        new GetDomainNameCommand({
          domainName: domain.givenDomainName,
        }),
      )
      return new DomainInfo(domainInfo)
    } catch (err) {
      const statusCode = err.$metadata?.httpStatusCode
      if (!statusCode || statusCode !== 404 || !silent) {
        throw new ServerlessError(
          `V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}`,
          ServerlessErrorCodes.domains.API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED,
          { originalMessage: err.message },
        )
      }
      Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`)
    }
  }

  async deleteCustomDomain(domain) {
    // Make API call
    try {
      await this.apiGateway.send(
        new DeleteDomainNameCommand({
          domainName: domain.givenDomainName,
        }),
      )
    } catch (err) {

View on GitHub (pinned to b9d7ea51c8)

Solutions

  1. Grant apigateway:GET on the domain resource to the deploying role.
  2. Retry the deploy - throttling and 5xx are typically transient; the SDK retryStrategy may already handle one round but a re-run is the safe fallback.
  3. If the error is a silent 404 surfacing unexpectedly, audit call sites passing silent=false and pass silent=true where absence is expected.
  4. Refresh AWS credentials (aws sts get-caller-identity) and confirm the region matches Globals.getRegion().
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm read permission and reachability before the lookup
async function canReadDomain(iam, accountId, region) {
  const policy = await iam.simulatePrincipalPolicy({
    PolicySourceArn: `arn:aws:iam::${accountId}:role/deploys`,
    ActionNames: ['apigateway:GET'],
    ResourceArns: [`arn:aws:apigateway:${region}::/domainnames/*`],
  })
  return policy.EvaluationResults?.[0]?.EvalDecision === 'allowed'
}

Try / catch

try {
  const info = await wrapper.getCustomDomain(domain, true)
  // info is undefined when the domain was a silent 404
} catch (err) {
  if (err.code === 'API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED') {
    const status = err.cause?.$metadata?.httpStatusCode
    if (status === 403) throw new Error('deploy role lacks apigateway:GET')
    if (status === 429 || status >= 500) { /* retry with backoff */ }
    else throw err
  } else throw err
}

Prevention

When it happens

Trigger: GetDomainNameCommand returns 403 (missing apigateway:GET), 429 throttling, 5xx service error, or a network failure with no httpStatusCode; or getCustomDomain is invoked with silent=false against a 404.

Common situations: Read-only deployment role lacking apigateway:GET permission; intermittent AWS throttling during a high-volume deploy; cross-account lookup where the assumed role cannot describe the domain; CI running with stale credentials that just expired.

Related errors


AI-assisted analysis of serverless/serverless@b9d7ea51c8 (2026-08-13). Data as JSON: /api/errors/957209440bb29c8e. Report an issue: GitHub.