FlowiseAI/Flowise · error · Error

Failed to get spreadsheet metadata: ${response.status} ${res

Error message

Failed to get spreadsheet metadata: ${response.status} ${response.statusText} - ${errorText}

What it means

Thrown by GoogleSheets.getSpreadsheetMetadata after a non-ok response from GET https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}. The message includes the HTTP status code, status text, and the raw response body, so it is the authoritative source for why the Google Sheets API rejected the metadata call.

Source

Thrown at packages/components/nodes/documentloaders/GoogleSheets/GoogleSheets.ts:325

                finaltext += `${doc.pageContent}\n`
            }
            return handleEscapeCharacters(finaltext, false)
        }
    }

    private async getSpreadsheetMetadata(spreadsheetId: string, accessToken: string): Promise<any> {
        const url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}`

        const response = await fetch(url, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            }
        })

        if (!response.ok) {
            const errorText = await response.text()
            throw new Error(`Failed to get spreadsheet metadata: ${response.status} ${response.statusText} - ${errorText}`)
        }

        return response.json()
    }

    private async getSheetData(spreadsheetId: string, range: string, valueRenderOption: string, accessToken: string): Promise<any> {
        const url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(range)}`
        const params = new URLSearchParams({
            valueRenderOption,
            dateTimeRenderOption: 'FORMATTED_STRING',
            majorDimension: 'ROWS'
        })

        const response = await fetch(`${url}?${params}`, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            }

View on GitHub (pinned to abe4a8601a)

Solutions

  1. Decode the status code in the message: 401 → re-authorize/refresh the OAuth token; 403 → add the service account email as a viewer on the sheet and confirm spreadsheets.readonly scope; 404 → correct the spreadsheetId; 429 → add exponential backoff or reduce request volume.
  2. Confirm the credential uses the same Google Cloud project that enabled the Google Sheets API.
  3. Ensure the access token is fetched at call time, not cached across long-lived sessions.
  4. Add retry-with-backoff around getSpreadsheetMetadata specifically for 429/503 responses.

Example fix

// before
const response = await fetch(url, { headers })
if (!response.ok) {
  const errorText = await response.text()
  throw new Error(`Failed to get spreadsheet metadata: ${response.status} ${response.statusText} - ${errorText}`)
}
// after - retry transient failures
async function fetchWithRetry(url, opts, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const r = await fetch(url, opts)
    if (r.ok || (r.status !== 429 && r.status < 500)) return r
    await new Promise(res => setTimeout(res, 2 ** i * 500))
  }
  const r = await fetch(url, opts)
  return r
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the token will likely work: call a cheap userinfo endpoint first
async function tokenLooksValid(accessToken) {
  const r = await fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + accessToken)
  return r.ok
}

Type guard

function isHttpError(e, statusStart = 400) {
  return typeof e?.message === 'string' && /Failed to get spreadsheet metadata:\s*(\d{3})/.test(e.message)
    && Number(RegExp.$1) >= statusStart
}

Try / catch

async function getMetadataWithRetry(id, token, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await loader.getSpreadsheetMetadata(id, token) }
    catch (e) {
      const m = e.message.match(/(\d{3})/)
      const code = m ? Number(m[1]) : 0
      if ((code === 429 || code >= 500) && i < attempts - 1) {
        await new Promise(r => setTimeout(r, 2 ** i * 500)); continue
      }
      throw e
    }
  }
}

Prevention

When it happens

Trigger: 401 when the access token is expired/revoked; 403 when the token lacks spreadsheets.readonly scope or the spreadsheet is not shared with the service account email; 404 when spreadsheetId is wrong or the sheet was deleted; 429 when the per-user/per-project quota is exhausted.

Common situations: Token auto-refresh not wired up so a cached token goes stale; service account email not added as a viewer on the target sheet; user copies only part of the spreadsheetId (e.g. includes the #gid fragment); hitting the Sheets API read quota during a large crawl.

Related errors


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/fa11657d2d9f8336. Report an issue: GitHub.