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.

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 type | Representable | Option |
|---|---|---|
| bigint, symbol, undefined, void | No | unrepresentable: "throw" (default), "any", or a function |
| date, map, set | No | Same option |
| transform | No | Same option |
| String, number, object, array, enum | Yes | None 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_keyrequired in your own schema. - Compare against
tools_schemain a test.
Sources
Related posts
More in Developers
- Which MCP server lets Claude Code or Cursor generate video and images?
MCP servers that let Claude Code and Cursor make video and images: Sume, fal, Replicate, Runway, Higgsfield. Endpoints, sign-in, billing, setup.
- Idempotency keys for AI video APIs: retry without paying twice
An idempotency key makes a retried create return the original run or job instead of a second paid one. How Sume's Idempotency-Key works on each API.
- Signed webhooks for Sume video runs: events, retries, verification
Sume sends one HMAC-SHA256 signed POST when a Format, Action, or Agent Completion run completes or fails. Verify the raw body and dedupe on request_id.
- Spend caps for unattended AI agents: how Sume bounds each run
An unattended agent has no one to approve spend, so Sume caps generation per run: required on Agent Completions, and up to $500 on Format runs.
Written by Sume