paperclipai/paperclip · error · Error

Discord ${operation} failed (HTTP ${response.status}${code ?

Error message

Discord ${operation} failed (HTTP ${response.status}${code ? `, code ${code}` : ""}${invalidFields.length > 0 ? `, invalid fields: ${invalidFields.join(", ")}` : ""})

What it means

discordJson() throws this when Discord returned a parseable body but a non-OK HTTP status. The message embeds the operation name (e.g. 'guild member lookup'), HTTP status, Discord error code, and up to 8 whitelisted invalid-field keys from the error body, giving a compact sanitized summary of why the Discord REST call failed.

Source

Thrown at server/src/services/chat-discord.ts:148

  });
  let body: unknown;
  try {
    body = await response.json();
  } catch {
    throw new Error("Discord returned an unreadable response");
  }
  if (!response.ok) {
    const errorBody =
      body && typeof body === "object" ? (body as DiscordErrorBody) : null;
    const codeValue = String(errorBody?.code ?? "");
    const code = /^\d{1,10}$/.test(codeValue) ? codeValue : null;
    const invalidFields =
      errorBody?.errors && typeof errorBody.errors === "object"
        ? Object.keys(errorBody.errors)
            .filter((field) => SAFE_DISCORD_ERROR_FIELDS.has(field))
            .slice(0, 8)
        : [];
    throw new Error(
      `Discord ${operation} failed (HTTP ${response.status}${code ? `, code ${code}` : ""}${invalidFields.length > 0 ? `, invalid fields: ${invalidFields.join(", ")}` : ""})`,
    );
  }
  return body as T;
}

function channelPermissions(input: {
  channel: DiscordChannel;
  guildId: string;
  memberId: string;
  memberRoleIds: Set<string>;
  roles: DiscordRole[];
}): bigint {
  let permissions = 0n;
  for (const role of input.roles) {
    if (role.id === input.guildId || input.memberRoleIds.has(role.id ?? "")) {
      permissions |= bigint(role.permissions);
    }

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read the embedded HTTP status and code: 401 -> fix the bot token, 403 -> fix permissions, 404 -> fix IDs, 429 -> add rate-limit backoff/retry-after handling
  2. If invalid fields are listed, correct those request payload fields
  3. Verify the bot is installed in the target guild with required scopes
  4. Wrap calls in retry logic honoring Discord's 429 retry_after

Example fix

// before
await discordJson(fetchGuild, "server lookup"); // raw failure surfaces
// after
try {
  await discordJson(fetchGuild, "server lookup");
} catch (err) {
  if (/HTTP 429/.test(err.message)) await sleep(getRetryAfter(err));
  else if (/HTTP 401/.test(err.message)) throw new ConfigError("Discord bot token invalid");
  else throw err;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  body = await discordJson(fetchFn, operation);
} catch (err) {
  const m = err.message.match(/HTTP (\d{3})(?:, code (\d+))?/);
  if (m && m[1] === "429") return retryAfter(getRetryAfterMs(err));
  if (m && m[1] === "401") throw new ConfigError("Discord bot token invalid or revoked");
  if (m && (m[1] === "403" || m[1] === "404")) throw new ConfigError("Check bot permissions and IDs");
  throw err;
}

Prevention

When it happens

Trigger: Any discordJson call ([user/application/guild] or [guild/member/roles/channels] lookups) where Discord responds 4xx/5xx: 401 for an invalid bot token, 403 for missing access, 404 for unknown IDs, 429 for rate limits, 400 with field errors for bad payloads.

Common situations: Expired or revoked bot token (401); bot kicked from the guild (404/403); missing intents or permissions; hitting Discord rate limits (429); malformed snowflake IDs reaching the API (400 with invalid fields).

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/c2bbcbcf7ab805ac. Report an issue: GitHub.