Zod z.toJSONSchema io: "input" for a Sume request body schema

Zod's z.toJSONSchema outputs the output type by default. Pass io: "input" to describe what a client may send, and note which Zod types cannot be represented.

5 min readSume
All posts

Writing a request schema in Zod and generating its JSON Schema keeps a client and its documentation aligned. The Zod JSON Schema page says z.toJSONSchema takes a target of draft-04, draft-07, draft-2020-12 (the default) or openapi-3.0, read 2026-10-03. By default it represents the output type; io: "input" extracts the input type instead. That distinction matters for a request body, where defaults and transforms change what a caller must send.

Input versus output

Consider a field with a default. As output it is always present; as input it is optional. If you document a Sume request body from the output shape, you would mark fields required that a caller may omit. Using io: "input" gives the shape a client sends.

The default dialect, draft-2020-12, is also the default JSON Schema dialect in the MCP specification's 2025-11-25 release (SEP-1613), per the MCP changelog, so the default target is a reasonable match for MCP tool schemas.

import { z } from "zod";

const Body = z.object({
  job_ids: z.array(z.string()).min(1).max(20),
  wait_for: z.enum(["all", "any"]).default("all"),
});

console.log(JSON.stringify(
  z.toJSONSchema(Body, { io: "input" }), null, 2
));

What Zod cannot represent

The page also describes an override callback and an experimental z.fromJSONSchema, so treat the reverse direction as unstable. The Zod 4 changelog prefers z.url() over the deprecated z.string().url(), which matters if your schema validates a webhook_url.

Zod JSON Schema limits, read 2026-10-03.
Zod typeRepresentableOption
bigint, symbol, undefined, voidNounrepresentable: "throw" (default), "any", or a function
date, map, setNoSame option
transformNoSame option
String, number, object, array, enumYesNone needed

Using it with Sume

Sume's tools and gates page lists the gates that a schema can document: idempotency_key is required on paid and write tools, and max_spend_usd is enforced only when provided. Encode both in your own request schema so a caller cannot forget them. Use tools_schema to read the server's authoritative definition and compare it with yours; the server's schema wins.

  • Generate with io: "input" for request bodies.
  • Fail loudly on unrepresentable types rather than silently using any.
  • Keep idempotency_key required in your own schema.
  • Compare against tools_schema in a test.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume