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
- Send the body as JSON with a top-level `config` object, e.g. {"config": {"type": "remote", "url": "https://...", "enabled": true}}.
- Ensure the request Content-Type is application/json so the body parses into an object rather than a string.
- Inspect the payload: remove double-serialization (config must be an object, not a JSON string).
- 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
- Always send { config: { type, url, enabled } } as a JSON object.
- Set Content-Type: application/json; never send stringified config strings.
- Validate payloads with Zod client-side before sending.
- Keep client payload shapes in sync with the API schema.
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.