FDE PulseFDE jobs open 463New in 7 days 17Companies hiring 54Remote-friendly 25%Median US pay $216kTop hirer Databricks 123
VI

The newspaper of the Forward Deployed Engineer

Tools

Zod: stop bad data from a customer's API before your agent reads it

On a customer site, one wrong field in an API response is enough to push an agent into a bad decision. A Zod schema at the entry point catches that error before the agent ever reads the data.

Zod: stop bad data from a customer's API before your agent reads it
Photo: Mohammad Rahmani / Unsplash

In brief

  • Zod validates data at runtime and infers TypeScript types from the same schema.
  • safeParse returns a result object instead of throwing, which suits a customer's unpredictable payloads.
  • Anyone building agent tools with the MCP TypeScript SDK will be using Zod, because the SDK requires it.
ShareLinkedInFacebookX
Decision tree: safeParse reports an error at the amount field, leading to the question "Client confirms?". If amount must be a number, keep z.number() and send the error to the client. If the client deliberately returns a string, use z.string() and then convert with Number() in one place.
When a client's API returns amount as a string, the right fix depends on the client's answer, not on coercing the type yourself.

Writing an MCP server in TypeScript for a customer? Then Zod is in your project before you have had a chance to pick a library. The official documentation for the MCP TypeScript SDK states that the SDK requires zod as a mandatory peer dependency for schema validation.

That detail says a lot about how agent tools are built today: before data reaches the agent, it has to pass through a schema. For an FDE, the entry point that matters most is the customer’s API. It is often a legacy system, thinly documented, and it sometimes returns things nobody expected.

Errors of this kind rarely crash the program straight away. They travel quietly into the prompt, into a calculation, into the agent’s decision, and surface three days later as a wrong answer nobody can trace. Zod lets you stop the error at the door instead of hunting for it at the end of the pipeline.

Zod does one job, but from both sides

The official site, zod.dev, describes Zod as a TypeScript-first schema validation library with static type inference. It was created by Colin McDonnell, is MIT-licensed and has zero external dependencies. Its API is immutable, and its core bundle is very small.

“From both sides” means this. At runtime, .parse checks the input and returns a typed deep clone, so unchecked data only becomes typed data once it has passed the schema.

At compile time, z.infer derives the TypeScript type from that same schema. You no longer have to maintain a hand-written interface alongside a separate validation function and worry about the two drifting apart.

An order payload through a schema

Picture a retail chain as the customer, with your agent reading orders to answer questions from customer-service staff. The API documentation says amount is a number. Then one day the response comes back as { "id": "A-1029", "amount": "12.5", "status": "paid" }: amount is a string.

import { z } from "zod";

const Order = z.object({
  id: z.string(),
  amount: z.number(),
  status: z.string(),
});

type Order = z.infer<typeof Order>;

const result = Order.safeParse(rawFromCustomerApi);

if (!result.success) {
  for (const issue of result.error.issues) {
    console.warn(issue.path, issue.code, issue.message);
  }
} else {
  runAgent(result.data); // result.data is typed as Order
}

There are two options here. .parse throws a ZodError when the input is wrong. safeParse does not throw; it returns a result object holding either the parsed data or a ZodError. With customer data, where errors are routine rather than exceptional, safeParse is usually the more sensible default.

The information in a ZodError is fairly detailed: each issue has a code, a path and a message. In the example above, path points straight at amount. Instead of telling the customer “the agent gave a wrong answer”, you can say “the orders endpoint returns amount as a string”, and their team knows immediately where to fix it.

Three traps, and how to fix the string amount

The most common trap is calling .parse directly on raw customer data inside the agent loop without a try/catch. A single bad payload throws a ZodError and the whole run stops. At this entry point, safeParse is safer.

The next trap is coercing the value yourself to “make it work” without asking the customer. Turning "12.5" into 12.5 looks harmless, but you do not yet know whether it is a transient bug, a new format or a different unit. Silent coercion hides exactly the signal the schema has just caught.

And do not keep a hand-written interface running in parallel with a schema. Sooner or later the two will drift; z.infer brings them back to one source.

For the amount case there are two correct fixes, depending on the customer’s answer. If the customer confirms amount must be a number, keep z.number(): safeParse rejects the payload, you log the path and pass the error to their team. If the customer confirms the endpoint now returns a string on purpose, record that in the schema and convert in one clearly visible place:

const OrderRaw = z.object({
  id: z.string(),
  amount: z.string(), // customer confirmed: amount is returned as a string
  status: z.string(),
});

const r = OrderRaw.safeParse(rawFromCustomerApi);
if (r.success) {
  const amount = Number(r.data.amount);
  if (Number.isNaN(amount)) {
    // string cannot be read as a number: reject, do not pass it to the agent
  } else {
    runAgent({ ...r.data, amount });
  }
}

The difference is that the conversion is now a decision agreed with the customer, commented and kept in one place in the code, rather than a silent patch nobody knows about.

Where can Zod not help you?

Zod checks the shape of data, not its meaning. If amount is the number 1250, the schema accepts it whether the customer counts in cents or in whole currency units. To find that out, you have to sit down with the customer’s engineers.

Nor does Zod decide for you what happens when data is wrong: reject it, log it and skip, or route it to a person. That is a product decision, and it should be discussed with the customer early. One more obvious limit: Zod lives in the TypeScript ecosystem, so if the customer’s pipeline is written in another language, you can only use Zod in the TypeScript parts.

Then there is the question of versions. Zod 4 has been announced as stable. The release notes say Zod 4 parses up to 14.71 times faster than Zod 3 in their benchmarks, and that the core bundle is about 57% smaller (2.3x).

If the customer’s codebase is still on Zod 3, treat the upgrade as a separate change with its own tests, and do not fold it into a bug-fix commit.

What to learn first, and how to show the skill

A sensible order is z.object with a few basic types, then safeParse, then reading ZodError, and finally z.infer. Those four are enough to build an entry-point validation layer for most integrations. After that, read how the MCP TypeScript SDK uses Zod to describe tool inputs, because that is exactly where the schema meets the agent.

When reading FDE job descriptions, if a posting mentions building MCP servers or agent tools in TypeScript, mention your Zod experience in the cover letter: the MCP TypeScript SDK requires Zod, so this is a skill you will use from week one.

On your CV, do not just write “knows Zod”. Write in terms of outcomes, for example: put schemas at the entry point for customer APIs, grouped errors by field, and shortened the time to find the root cause when the agent answered wrongly.

Many people think a good FDE is someone who writes good prompts. But once an agent goes into production at a customer site, what keeps it stable is often a schema that runs before the prompt.

Was this article useful?

Use with your AI assistantAsk Claude ↗Ask ChatGPT ↗
5 sources
Read next on the roadmap · Stage 2: Broad engineeringUsing Claude Code, Codex and Cursor in a client's repo: check the account before you write a promptAll three coding assistants can be restricted. Their defaults differ, though, and the exceptions most likely to put an FDE in breach of a client's policy are buried in documentation few people read.