koala73/worldmonitor · error · ApiError

At least one of domain or country is required

Error message

At least one of domain or country is required

What it means

getIntelTimeline throws ApiError 400 when, after validateHistoryScope normalization, both scope.domain and scope.country are empty. Normalization runs BEFORE this check specifically so a whitespace-only country cannot satisfy the 'at least one scope' requirement and then silently match nothing. The endpoint is premium-gated at the gateway, so callers reaching this error already passed entitlement.

Source

Thrown at server/worldmonitor/intelligence/v1/get-intel-timeline.ts:48

 * (ApiError, surfaced by server/error-mapper.ts) tells the caller their
 * request was wrong instead of blaming the backend. buf.validate cannot
 * express "at least one of", so the check lives in code.
 *
 * A store failure returns an empty result with `upstreamUnavailable: true`
 * rather than a 5xx, so the gateway does not cache an empty timeline for the
 * slow tier's TTL. Premium-gated at the gateway via PREMIUM_RPC_PATHS +
 * ENDPOINT_ENTITLEMENTS.
 */
export const getIntelTimeline: IntelligenceServiceHandler['getIntelTimeline'] = async (
  _ctx: ServerContext,
  req: GetIntelTimelineRequest,
): Promise<GetIntelTimelineResponse> => {
  // Normalize BEFORE the scope check: a whitespace-only country would
  // otherwise satisfy "at least one scope" and then match nothing.
  const scope = validateHistoryScope(req, MAX_LIMIT);
  const { domain, country } = scope;
  if (!domain && !country) {
    throw new ApiError(400, 'At least one of domain or country is required', '');
  }

  const limit = resolveLimit(scope.limit, DEFAULT_LIMIT, MAX_LIMIT);
  const result = await cacheSuccessfulHistoryRead('timeline', {
    domain, country, from: scope.from, to: scope.to, limit,
  }, () => intelHistoryTimeline({ ...scope, limit }));
  if (!result) {
    return { records: [], partial: false, upstreamUnavailable: true };
  }

  return { records: result.records, partial: result.partial, upstreamUnavailable: false };
};

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Supply a non-empty domain or country (or both) on every call
  2. Enforce the one-of rule in the client: if both filters are cleared, disable the request instead of submitting
  3. Trim scope inputs client-side so accidental whitespace-only values are caught early
Defensive patterns

Strategy: validation

Validate before calling

const domain = req.domain?.trim().toLowerCase();
const country = req.country?.trim();
if (!domain && !country) {
  throw new ClientError('Timeline requires a domain or a country scope');
}

Type guard

function isMissingScopeError(body: unknown): boolean {
  return (body as { message?: string })?.message === 'At least one of domain or country is required';
}

Try / catch

try {
  await getIntelTimeline({ domain, country });
} catch (e) {
  if (e instanceof HttpError && e.status === 400 && isMissingScopeError(e.body)) {
    return disableSubmit('Choose a domain or country filter');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling /api/intelligence/v1/get-intel-timeline with no domain and no country; sending country: ' ' (whitespace only) and nothing else; sending only from/to date range and limit, assuming dates alone scope the query.

Common situations: UI 'clear filters' action submits the form with every scope field emptied; API explorer request built without copying the scope parameter; client treats country as optional and domain as optional without enforcing one-of.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/3939136f631e6ce4. Report an issue: GitHub.