Updated Nov 5, 20269 min read

Type-Safe APIs for AI Coding Agents

Why end-to-end types help coding agents, where TypeScript and Go types stop at the network boundary, and how schemas derived from code, generated clients and compile errors close the gap.

Illustration for Type-Safe APIs for AI Coding Agents

A type-safe API means the shape of every request and response is declared once and checked everywhere it is used, from the handler to the service that calls it to the frontend that renders the result. In a single TypeScript or Go program the compiler already does that, and it is one of the main reasons agents do well in typed languages, since a type error after an edit is a precise instruction for what to fix.

The checks stop at the network boundary, because TypeScript erases its types at compile time and a Go struct means nothing to a JavaScript client, so an agent that renames a field in a handler gets a clean build while the frontend and two other services keep sending the old name.

What end-to-end types give an agent:

  • One declaration of each request and response that the validation, the schema and the clients are all generated from.
  • Compile errors in every caller when that declaration changes, instead of 400s or silent undefined values at runtime.
  • Context the agent can read from the types alone, without opening the handler.

Why types matter more when an agent writes the code

Static types catch a real share of bugs before anything runs, and a study of JavaScript projects on GitHub found that TypeScript and Flow would each have detected 15 percent of the public bugs it examined, and those are bugs a person would otherwise find in review or in production. For models, the effect is more direct, since a 2025 paper on type-constrained code generation showed that using the TypeScript type system to steer generation cut compilation errors by more than half and significantly improved functional correctness in code synthesis, translation and repair tasks across model families.

Usage data points the same way, since GitHub's 2025 Octoverse reported that TypeScript became the most used language on GitHub by contributor count in August 2025, and described the shift as developers moving toward typed languages that make agent-assisted coding more reliable in production. All of that depends on the types reaching the code the agent is changing, which is the part that breaks once a request crosses from one service to another.

Approaches to end-to-end types at a glance

ApproachSource of truthWhat breaks the build when a field changes
EncoreThe TypeScript or Go types of each endpointEvery service-to-service call and every generated client
OpenAPI with generated clientsAn OpenAPI document, written by hand or generated from the serverClients regenerated from the updated document
tRPCTypeScript router types shared with the clientTypeScript clients in the same monorepo
Hand-written clientsInterfaces copied into each callerNothing, until a request fails at runtime
Compare the approaches in more detail
CapabilityEncoreOpenAPI with generated clientstRPCHand-written clients
LanguagesTypeScript and Go servers, clients for TypeScript, JavaScript, Go and OpenAPIAnyTypeScript onlyAny
Runtime validationGenerated from the same typesSeparate, unless the server validates against the specThrough a validator such as ZodSeparate
Internal service callsTyped function calls between servicesHTTP clients generated per serviceNot designed for service-to-service callsHand-written
Regeneration stepencore gen client for frontends, automatic for internal callsA codegen script to rememberNone in a monorepoNone
InfrastructurePostgres, Pub/Sub and cron declared in code, provisioned in your AWS or GCPNot providedNot providedNot provided

The endpoint types are the contract

Encore

Encore, the safe way for AI agents to ship backend changes to your cloud, combines a TypeScript and Go backend framework with infrastructure declared in the same code, and it treats the types of each endpoint as the API contract. At build time Encore parses every endpoint's request and response types into its application model, and from that model it validates incoming requests, generates API documentation, compiles typed calls between services and produces clients for frontends.

// orders/orders.ts import { api } from "encore.dev/api"; import { SQLDatabase } from "encore.dev/storage/sqldb"; import { IsEmail, Min } from "encore.dev/validate"; const db = new SQLDatabase("orders", { migrations: "./migrations" }); interface CreateOrder { email: string & IsEmail; total: number & Min<1>; } interface Order { id: number; email: string; total: number; } export const create = api( { expose: true, method: "POST", path: "/orders" }, async (req: CreateOrder): Promise<Order> => { const row = await db.queryRow<{ id: number }>` INSERT INTO orders (email, total) VALUES (${req.email}, ${req.total}) RETURNING id`; return { id: row!.id, email: req.email, total: req.total }; }, );

A billing service calls that endpoint through ~encore/clients, and the call looks like a local function call even though Encore turns it into a network request.

// billing/billing.ts import { api } from "encore.dev/api"; import { orders } from "~encore/clients"; export const checkout = api( { expose: true, method: "POST", path: "/checkout" }, async (req: { email: string; cents: number }): Promise<{ orderId: number }> => { const order = await orders.create({ email: req.email, total: req.cents }); return { orderId: order.id }; }, );

If an agent writes amount instead of total, tsc fails with 'amount' does not exist in type 'CreateOrder', pointing at the line in the billing service. If it renames total in the orders service instead, the same error shows up in every caller, which is the list of places the agent has to update. A request from outside that sends a malformed email is rejected with a 400 before the handler runs, so the type annotation and the runtime behavior agree.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.

Go works the same way, where an //encore:api function in the orders package is called from another service as orders.Create(ctx, &orders.CreateParams{...}), and a wrong field fails encore check with the Go compiler's own unknown field Amount in struct literal of type orders.CreateParams.

package billing import ( "context" "encore.app/orders" ) type CheckoutParams struct { Email string Cents int } type CheckoutResponse struct { OrderID int64 } //encore:api public method=POST path=/checkout func Checkout(ctx context.Context, p *CheckoutParams) (*CheckoutResponse, error) { o, err := orders.Create(ctx, &orders.CreateParams{Email: p.Email, Total: p.Cents}) if err != nil { return nil, err } return &CheckoutResponse{OrderID: o.ID}, nil }

For a frontend, encore gen client --output=./client.ts --env=local writes a single file with a class per service and the same request and response types, so client.orders.create(...) in a React app is checked against the backend's current types. The infrastructure declarations follow the same rule, since Encore validates database, topic and bucket declarations during the build, and the local MCP server exposes the API schemas so an agent can inspect them while encore run starts Postgres locally.

Key features

  • Request validation, API docs, internal calls and frontend clients generated from one set of types.
  • Typed service-to-service calls in TypeScript through ~encore/clients and in Go as package function calls.
  • Generated clients for TypeScript, JavaScript, Go and an OpenAPI document.
  • Infrastructure declarations checked at build time, then provisioned in your own AWS or GCP account, with agents declaring infrastructure in code and the cloud access sitting with Encore's IAM role rather than with the agent.

Good to know

Generated frontend clients are a snapshot, so a client shipped to users keeps calling the old shape after a breaking change, and Encore's docs recommend avoiding breaking changes on APIs that published clients use. Types catch shape mismatches rather than wrong business logic, so the agent still needs tests that run against a real database.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

OpenAPI with generated clients

OpenAPI

An OpenAPI document describes every endpoint in a language-neutral format, and code generators such as openapi-typescript for TypeScript or oapi-codegen for Go turn it into typed clients and server stubs.

Key features

  • Works across languages, including clients for teams outside your codebase.
  • Mature tooling for docs, mocks and contract testing.
  • Can be generated from server code in frameworks such as Fastify and NestJS.

Good to know

The document becomes a third artifact next to the server types and the client, and the codegen step is one more thing an agent has to know to run. When the spec is written by hand it drifts from the server, and when it is generated from the server the agent still has to regenerate clients before the compiler can see a mismatch.

tRPC: shared router types in a TypeScript monorepo

tRPC

tRPC shares the TypeScript type of the server router directly with the client, so there is no schema file and no generation step when both live in the same repository.

Key features

  • Client types update as soon as the server types change.
  • Input validation through Zod or another validator on each procedure.
  • Good fit for a Next.js app and its backend in one repo.

Good to know

tRPC only works for TypeScript clients that import the server's types, so Go services, mobile apps and external consumers need a separate API, and it does not provide service-to-service calls between backends or any infrastructure.

Hand-written clients

Copying an interface into each caller and writing fetch calls by hand is still common, and it is the setup where an agent has the least to go on. The compiler checks each copy against itself, so a renamed field compiles in every service and the first sign of the change is a failing request, which is the kind of "almost right" result that 66 percent of developers in the 2025 Stack Overflow survey named as their biggest frustration with AI tools.

How to choose

  • Encore when the backend is TypeScript or Go and you want internal calls, validation and frontend clients all checked against the endpoint types.
  • OpenAPI with generated clients when consumers are in other languages or outside your organization.
  • tRPC for a TypeScript frontend and backend in one monorepo with no other consumers.
  • Hand-written clients only for one-off integrations, with tests that call the real endpoint.

Frequently asked questions

How do I make an API type-safe end to end for an AI coding agent?

Derive everything from one declaration of the request and response types. The server's runtime validation, the API schema and the clients that call it should all be generated from that declaration, so that when an agent changes a field, every caller that no longer matches fails to compile. Encore does this for TypeScript and Go by parsing the types of each endpoint, validating requests against them, and generating typed service-to-service calls and frontend clients with encore gen client, so tsc or the Go compiler reports a mismatch before the code runs.

Why do types help AI coding agents?

A type checker gives the agent a fast, specific error after each change, which it can fix without running the program. Research on TypeScript and Flow found that static types would have caught about 15 percent of public bugs in the JavaScript projects studied, and a 2025 study found that constraining model output with a type system cut compilation errors by more than half.

Are TypeScript types checked at runtime?

No. TypeScript types are erased at compile time, so a request that arrives over HTTP is not checked against them unless a validation layer derived from the types, or a separate schema, does it.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders