Budibase/budibase · error · HTTPError

Slack app configuration refresh token is required

Error message

Slack app configuration refresh token is required

What it means

save() in slackAppConfig.ts also requires a non-empty refresh token, since tokens are rotated automatically via rotateSlackConfigToken. When the trimmed refreshToken is empty it throws this 400 HTTPError before contacting Slack or writing to the DB.

Source

Thrown at packages/server/src/sdk/workspace/ai/slackAppConfig.ts:118

  }

  const rotated = await rotateSlackConfigToken({
    refreshToken: decodeSecret(config.refreshToken),
  })
  return decodeSecret((await saveRotatedConfig(config, rotated)).configToken)
}

export const save = async (configToken: string, refreshToken: string) => {
  const db = getWorkspaceDB()
  const existing = await fetch()
  const now = new Date().toISOString()
  const trimmedToken = configToken.trim()
  const trimmedRefreshToken = refreshToken.trim()
  if (!trimmedToken) {
    throw new HTTPError("Slack app configuration token is required", 400)
  }
  if (!trimmedRefreshToken) {
    throw new HTTPError(
      "Slack app configuration refresh token is required",
      400
    )
  }
  if (trimmedToken === PASSWORD_REPLACEMENT && !existing?.configToken) {
    throw new HTTPError("Slack app configuration token is required", 400)
  }
  if (trimmedRefreshToken === PASSWORD_REPLACEMENT && !existing?.refreshToken) {
    throw new HTTPError(
      "Slack app configuration refresh token is required",
      400
    )
  }

  const rotated = await rotateSlackConfigToken({
    refreshToken:
      trimmedRefreshToken === PASSWORD_REPLACEMENT && existing?.refreshToken
        ? decodeSecret(existing.refreshToken)

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Supply the Slack app refresh token alongside the config token and retry.
  2. Verify the client actually sends the refreshToken field (not undefined) in the payload.
  3. If rotating an existing config, use the PASSWORD_REPLACEMENT sentinel instead of an empty string so the stored refresh token is reused.

Example fix

// before
await sdk.ai.slackAppConfig.save(configToken, "")
// after
await sdk.ai.slackAppConfig.save(configToken, refreshToken)
Defensive patterns

Strategy: validation

Validate before calling

if (!refreshToken || !refreshToken.trim()) throw new Error("refreshToken is required")
await sdk.ai.slackAppConfig.save(configToken, refreshToken)

Type guard

const hasToken = (v: unknown): v is string => typeof v === "string" && v.trim().length > 0

Try / catch

try {
  await sdk.ai.slackAppConfig.save(configToken, refreshToken)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400) {
    // prompt user for the refresh token
  }
  throw e
}

Prevention

When it happens

Trigger: Calling save(configToken, refreshToken) with refreshToken being "" or whitespace-only.

Common situations: Only pasted the config token and left the refresh token field empty; UI forgot to render/send the refresh token input; stored secret got wiped during migration.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/a9feb7e5a5806016. Report an issue: GitHub.