koala73/worldmonitor · warning · ApiError

Specify a fast/slow tier or one registered bootstrap key

Error message

Specify a fast/slow tier or one registered bootstrap key

What it means

buildRegistry enforces exactly one valid request shape: either a tier ('fast' or 'slow' with no explicit keys), or no tier plus exactly one key registered in BOOTSTRAP_CACHE_KEYS. Any other combination throws ApiError(400, 'Specify a fast/slow tier or one registered bootstrap key').

Solutions

  1. Send { tier: 'fast' } or { tier: 'slow' } with empty keys for the full tier registry
  2. For a single dataset, send exactly one key from BOOTSTRAP_CACHE_KEYS and omit tier
  3. Re-sync the client's key list against the server's BOOTSTRAP_CACHE_KEYS after deployments

Example fix

// before
await getBootstrapData(ctx, { tier: 'fast', keys: ['earthquakes'] });
// after
await getBootstrapData(ctx, { tier: 'fast' }); // or { keys: ['earthquakes'] } without tier
Defensive patterns

Strategy: validation

Validate before calling

const valid = (req.tier === 'fast' || req.tier === 'slow') && (!req.keys || req.keys.length === 0)
  || (!req.tier && req.keys?.length === 1 && req.keys[0] in BOOTSTRAP_CACHE_KEYS);
if (!valid) throw new Error('send tier fast/slow alone, or exactly one registered bootstrap key');

Try / catch

try {
  return await getBootstrapData(ctx, req);
} catch (e) {
  if (e instanceof ApiError && e.status === 400) {
    return await getBootstrapData(ctx, { tier: 'fast' }); // safe default tier
  }
  throw e;
}

Prevention

When it happens

Trigger: Sending tier AND keys together; tier='medium' or any unknown tier; no tier with zero or multiple keys; no tier with a single key not present in BOOTSTRAP_CACHE_KEYS.

Common situations: Client accumulating selected keys across UI toggles and sending several at once; typo in a bootstrap key name; server adding/removing a cache key so a previously valid client key is no longer registered; caching a default tier value that is not 'fast'/'slow'.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/970a46c6c3658bf9. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/infrastructure/v1/get-bootstrap-data.ts:25

} from '../../../../src/generated/server/worldmonitor/infrastructure/v1/service_server';
import { BOOTSTRAP_CACHE_KEYS, BOOTSTRAP_TIERS } from '../../../_shared/cache-keys';
// @ts-expect-error — Edge-safe JS helper
import { sanitizeBootstrapValue } from '../../../../api/_bootstrap-public-payload.js';
// @ts-expect-error — Edge-safe JS helper
import { extraCanadaAlertsCutoverReadKeys, canadaAlertsCutoverFallbackValue } from '../../../../api/_canada-alerts-cutover.js';
import { getCachedJsonBatch } from '../../../_shared/redis';

// Iran-events domain sunset (war ended 2026-07). Default OFF: this RPC bootstrap
// surface must also stop shipping iranEvents, mirroring api/bootstrap.js. It
// reads the SHARED BOOTSTRAP_CACHE_KEYS, so the gate lives here. Set
// IRAN_EVENTS_ENABLED=true to restore. See api/health.js.
const IRAN_EVENTS_ENABLED = (process.env.IRAN_EVENTS_ENABLED ?? 'false').toLowerCase() === 'true';

function buildRegistry(req: GetBootstrapDataRequest): Record<string, string> {
  if ((req.tier && req.keys.length > 0)
    || (req.tier && req.tier !== 'fast' && req.tier !== 'slow')
    || (!req.tier && (req.keys.length !== 1 || !Object.prototype.hasOwnProperty.call(BOOTSTRAP_CACHE_KEYS, req.keys[0]!)))) {
    throw new ApiError(400, 'Specify a fast/slow tier or one registered bootstrap key', '');
  }
  let registry: Record<string, string>;
  if (req.tier === 'slow' || req.tier === 'fast') {
    registry = Object.fromEntries(
      Object.entries(BOOTSTRAP_CACHE_KEYS).filter(([key]) => BOOTSTRAP_TIERS[key] === req.tier),
    );
  } else {
    registry = Object.fromEntries(
      Object.entries(BOOTSTRAP_CACHE_KEYS).filter(([key]) => req.keys.includes(key)),
    );
  }

  if (!IRAN_EVENTS_ENABLED) delete registry.iranEvents;
  return registry;
}

/**
 * Fetch one named dataset or a fixed public tier; never enumerate the full registry.

View on GitHub (pinned to 7d06c8633d)