different-ai/openwork · error · ApiError

invalid_payload

invalid_payload

Error message

config is required

What it means

normalizeCloudMcpConfig validates the request body's config field: if it is not a plain object (isRecord fails), it throws this 400 invalid_payload ApiError. The endpoint requires a structured MCP config object (type defaults to "remote", url/enabled normalized when present).

Source

Thrown at apps/server/src/cloud-mcp-health.ts:739

  // the caller keeps the richer stage-tagged validation error.
  if (!normalized) return true;
  let url: URL;
  try {
    url = new URL(normalized);
  } catch {
    return true;
  }
  if (isLoopbackHostname(url.hostname)) return true;
  if (url.protocol !== "https:") return false;
  if (BUILT_IN_CLOUD_MCP_ORIGINS.has(url.origin)) return true;
  const { readActivatedEnterpriseDenOrigin } = await import("./enterprise-den-origin.js");
  const enterpriseOrigin = await readActivatedEnterpriseDenOrigin();
  return enterpriseOrigin !== null && url.origin === enterpriseOrigin;
}

function normalizeCloudMcpConfig(input: unknown): Record<string, unknown> {
  if (!isRecord(input)) {
    throw new ApiError(400, "invalid_payload", "config is required");
  }
  const type = input.type ?? "remote";
  const url = readString(input.url);
  const output: Record<string, unknown> = { type };
  if (url) output.url = normalizeCloudEndpointUrl(url) ?? url;
  const enabled = readBoolean(input.enabled);
  if (enabled !== undefined) output.enabled = enabled;
  const headers = normalizeStringRecord(input.headers);
  if (headers) output.headers = headers;
  if (input.oauth === false) output.oauth = false;
  else if (input.oauth === true) output.oauth = {};
  else if (isRecord(input.oauth)) output.oauth = input.oauth;
  const timeout = readNumber(input.timeout);
  if (timeout !== undefined) output.timeout = timeout;
  return output;
}

function strictCloudMcpDesiredConfigProblem(config: Record<string, unknown>, metadata: CloudMcpDesiredMetadata): CloudMcpValidationProblem | null {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Send the body as JSON with a top-level `config` object, e.g. {"config": {"type": "remote", "url": "https://...", "enabled": true}}.
  2. Ensure the request Content-Type is application/json so the body parses into an object rather than a string.
  3. Inspect the payload: remove double-serialization (config must be an object, not a JSON string).
  4. Check the API/SDK version for the expected payload shape.

Example fix

// before
fetch(url, { body: JSON.stringify({ config: JSON.stringify(cfg) }) })
// after
fetch(url, { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ config: cfg }) })
Defensive patterns

Strategy: validation

Validate before calling

function assertConfig(body: unknown): asserts body is { config: Record<string, unknown> } {
  if (typeof body !== "object" || body === null ||
      typeof (body as Record<string, unknown>).config !== "object" ||
      (body as Record<string, unknown>).config === null) {
    throw new Error("body must contain a config object");
  }
}

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  await api.updateCloudMcp(id, payload);
} catch (e) {
  if (isApiError(e) && e.code === "invalid_payload" && e.message === "config is required") {
    // fix request body shape: { config: {...} }
  }
}

Prevention

When it happens

Trigger: POSTing to the cloud MCP config endpoint with a missing config field, a JSON string instead of an object, an array, null, or a top-level payload where config was nested one level deeper than expected.

Common situations: Client sending form-encoded or stringified body without the server parsing JSON; forgetting the `config` wrapper key; sending `config: "..."` after double-serializing; SDK version mismatch sending the old payload shape.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/a17146242e2781a3. Report an issue: GitHub.