The AdCP client library provides runtime validation schemas using Zod, automatically generated from the official AdCP protocol schemas.
npm install @adcp/sdk zodimport { MediaBuySchema, GetProductsRequestSchema } from '@adcp/sdk';
// Validate data
const result = MediaBuySchema.safeParse(data);
if (result.success) {
console.log('Valid!', result.data);
} else {
console.error('Errors:', result.error.issues);
}- ✅ Runtime validation - Catch data issues at runtime, not just compile time
- 🔒 Type safety - Infer TypeScript types from schemas
- 🎯 Error messages - Detailed validation errors with field paths
- 📝 Form integration - Works with React Hook Form, Formik, etc.
- 🌐 API validation - Validate requests/responses
MediaBuySchema,ProductSchema,CreativeAssetSchema,TargetingSchema
GetProductsRequestSchema/GetProductsResponseSchemaCreateMediaBuyRequestSchema/CreateMediaBuyResponseSchemaSyncCreativesRequestSchema/SyncCreativesResponseSchema- And all other AdCP tasks...
import { GetProductsRequestSchema } from '@adcp/sdk';
function callGetProducts(request: unknown) {
const validated = GetProductsRequestSchema.parse(request);
return agent.getProducts(validated); // Type-safe!
}import { GetProductsResponseSchema } from '@adcp/sdk';
async function fetchProducts() {
const response = await agent.getProducts(request);
const validated = GetProductsResponseSchema.parse(response);
return validated.products; // Guaranteed valid!
}import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { CreateMediaBuyRequestSchema } from '@adcp/sdk';
function MediaBuyForm() {
const { register, handleSubmit } = useForm({
resolver: zodResolver(CreateMediaBuyRequestSchema)
});
// Form data is automatically validated!
}app.post('/api/products', async (req, res) => {
try {
const request = GetProductsRequestSchema.parse(req.body);
const response = await agent.getProducts(request);
res.json(GetProductsResponseSchema.parse(response));
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ errors: error.issues });
}
}
});const PartialMediaBuy = MediaBuySchema.partial(); // All fields optionalconst ProductWithCache = ProductSchema.extend({
_cached_at: z.string().datetime()
});const NormalizedRequest = GetProductsRequestSchema.transform(data => ({
...data,
brief: data.brief?.trim().toLowerCase()
}));Zod schemas are automatically generated when you update types:
# Sync schemas from protocol and generate everything
npm run sync-schemas && npm run generate-typesThis single command:
- Downloads latest AdCP JSON schemas
- Generates TypeScript types
- Generates Zod schemas (automatic)
See VALIDATION_WORKFLOW.md for CI integration details.
const result = MediaBuySchema.safeParse(invalidData);
if (!result.success) {
result.error.issues.forEach(issue => {
console.log(`Field: ${issue.path.join('.')}`);
console.log(`Error: ${issue.message}`);
});
}- Use at API boundaries and user input
- Zod schemas are immutable - safe to cache
- Consider skipping validation in performance-critical production paths
import { MediaBuy, MediaBuySchema } from '@adcp/sdk';
import { z } from 'zod';
type MediaBuyInferred = z.infer<typeof MediaBuySchema>;
// MediaBuyInferred is compatible with MediaBuy type!If you're building a platform that receives AdCP tool calls (a seller/publisher), you need request types for your handler signatures and schemas for runtime validation. Both are exported from @adcp/sdk.
| Pattern | Meaning | Example |
|---|---|---|
{Tool}Request |
Parameters for a tool call | CreateMediaBuyRequest |
{Tool}Response |
Return value from a tool call | CreateMediaBuyResponse |
{Noun}Request |
Creation-shaped nested object (required fields) | PackageRequest |
{Noun} (no suffix) |
Response-shaped object (from core.generated) |
Package |
*Schema suffix |
Zod runtime validator for any of the above | CreateMediaBuyRequestSchema |
The Request suffix on PackageRequest means "creation-shaped" — it has required fields like buyer_ref, product_id, budget. The plain Package type is response-shaped with package_id and most fields optional.
| Tool | Request Type | Schema | Required Fields |
|---|---|---|---|
get_products |
GetProductsRequest |
GetProductsRequestSchema |
buying_mode |
list_creative_formats |
ListCreativeFormatsRequest |
ListCreativeFormatsRequestSchema |
(all optional filters) |
create_media_buy |
CreateMediaBuyRequest |
CreateMediaBuyRequestSchema |
buyer_ref, account, brand, start_time, end_time |
| (nested) | PackageRequest |
PackageRequestSchema |
buyer_ref, product_id, budget, pricing_option_id |
update_media_buy |
UpdateMediaBuyRequest |
UpdateMediaBuyRequestSchema |
(identify by media_buy_id or buyer_ref) |
sync_creatives |
SyncCreativesRequest |
SyncCreativesRequestSchema |
account, creatives |
get_media_buy_delivery |
GetMediaBuyDeliveryRequest |
GetMediaBuyDeliveryRequestSchema |
(all optional filters) |
import {
// TypeScript types for handler signatures
CreateMediaBuyRequest,
CreateMediaBuyResponse,
PackageRequest,
TargetingOverlay,
// Zod schema for runtime validation
CreateMediaBuyRequestSchema,
} from '@adcp/sdk';
function handleCreateMediaBuy(rawParams: unknown): CreateMediaBuyResponse {
// Validate and parse the incoming request
const request: CreateMediaBuyRequest = CreateMediaBuyRequestSchema.parse(rawParams);
// All fields are now typed — IDE autocomplete works
const { buyer_ref, account, brand, start_time, end_time } = request;
// Nested types are also fully typed
for (const pkg of request.packages ?? []) {
// pkg is PackageRequest — buyer_ref, product_id, budget are required
const overlay: TargetingOverlay | undefined = pkg.targeting_overlay;
if (overlay?.geo_countries) {
// geo_countries is string[]
}
}
// Return a typed response (CreateMediaBuyResponse = CreateMediaBuySuccess | CreateMediaBuyError)
return { media_buy_id: 'mb_123', buyer_ref, packages: [/* ... */] };
}// Types — for handler signatures and return values
import type {
CreateMediaBuyRequest, CreateMediaBuyResponse,
GetProductsRequest, GetProductsResponse,
SyncCreativesRequest, SyncCreativesResponse,
PackageRequest, TargetingOverlay, FrequencyCap,
} from '@adcp/sdk';
// Schemas — for runtime validation
import {
CreateMediaBuyRequestSchema,
GetProductsRequestSchema,
SyncCreativesRequestSchema,
} from '@adcp/sdk';See examples/zod-validation-example.ts for complete examples.
Yes, downstream users automatically get Zod schemas! Here's how:
When you npm publish, the package includes:
@adcp/sdk/
├── dist/lib/types/schemas.generated.js ← Zod schemas (compiled)
├── dist/lib/types/schemas.generated.d.ts ← Type definitions
└── dist/lib/index.js ← Re-exports schemas
When someone installs @adcp/sdk, they get:
// Works immediately after npm install
import { MediaBuySchema } from '@adcp/sdk';
const result = MediaBuySchema.safeParse(data);No extra steps needed! The compiled Zod schemas are part of the published package.
The package.json declares zod as a peer dependency:
{
"peerDependencies": {
"zod": "^3.22.4"
}
}This means:
- Users must install
zodseparately:npm install @adcp/sdk zod - NPM shows a warning if
zodis missing - Users can choose their
zodversion (within range)
After publishing, downstream users can verify:
npm install @adcp/sdk zod
# Check what's in the package
npm ls @adcp/sdk
# Verify exports work
node -e "console.log(require('@adcp/sdk').MediaBuySchema)"Solution: Install zod as a peer dependency: npm install zod
Some complex nested schemas may need:
const FlexibleProduct = ProductSchema.passthrough(); // Allow extra fieldsnpm run sync-schemas && npm run generate-types
# Then check for breaking changes in your codeFor CI integration and schema generation workflow, see VALIDATION_WORKFLOW.md.