Types and validation
These pages show what each stack gets from your contract: the types you code against and the validators that refuse a bad request before your handler runs. They all use the same schema, CreateTask, trimmed here.
# contract/api.yaml, under schemasCreateTask: type: object required: [title] properties: title: { type: string, minLength: 1, maxLength: 200 } effort: { type: integer, minimum: 0 }Each schema becomes one type and one validator per direction: input (what a client sends) and output (what your handler returns). Each operation also gets one input validator over its path, query, headers and body. An input validator refuses a key the contract does not declare.
What your stack gets
Section titled “What your stack gets”| Stack | Types | Validator | You pick the validator? |
|---|---|---|---|
| NestJS | TypeScript types | zod (default), yup or class-validator | Yes, with the validators entry |
| Express, Hono | TypeScript types | zod or yup | Yes, with the validators entry |
| Fastify | TypeScript types | zod or yup, or Ajv when you link none | Yes |
| AdonisJS | TypeScript types | zod or yup, or VineJS when you link none | Yes |
| Spring Boot | Java records | A generated structural validator | No |
| Laravel | PHP classes | Laravel rules | rules only |
| Symfony | PHP classes | Symfony constraints | rules only |
| FastAPI, Django Ninja | Pydantic models | Pydantic | rules only |
| Django REST Framework | dataclasses | DRF serializers | rules only |
Server guides: NestJS, Express, Fastify, AdonisJS, Hono, Spring Boot, Laravel, Symfony, FastAPI, Django REST Framework, Django Ninja.
Client guides use the TypeScript types through the fetch client. The generators that write zod, yup and class-validator validators serve the server only.
To change the library on a Node server, see Switch the validator.
Native or consistent rules
Section titled “Native or consistent rules”By default (rules: native) the generated validator uses the library’s own rules, such as .min(), uuid or Field(max_length=...). The library behaves as you know it, and in a few places it differs from the contract: how it counts the length of an emoji, or what it accepts as a date. Each library page lists them.
Set rules: seampact-consistent to get the contract’s exact checks instead. Every client, server and language then gives the same answer.
# seampact.config.yaml, under targets - id: validators use: "@seampact/codegen-yup" apis: [notes] options: rules: seampact-consistentThe option exists on the zod, yup, class-validator, AdonisJS, Laravel, Symfony, FastAPI, Django REST Framework and Django Ninja targets. Spring Boot has no such option: its Java page says what is checked. The error body is the same in both modes.
When a check fails
Section titled “When a check fails”A request the validator refuses never reaches your handler. The server answers 400 with the error api.schema_invalid, as application/problem+json.
{ "type": "api.schema_invalid", "status": 400, "code": "api.schema_invalid", "nature": "refused", "issues": [ { "in": "body", "pointer": "/title", "rule": "minLength" }, { "in": "body", "pointer": "/effort", "rule": "minimum" } ]}Each issue has three members.
in: where the value sits:path,query,headerorbody.pointer: the value inside that part, as a JSON pointer. A pointer of""is the whole body.rule: the contract rule that failed, such asrequired,type,minLength,maxLength,minimum,format,enumorunknownKeys. It is never the library’s own error code, so a client reads the same rule from every server.
The reply lists every issue, up to 100. An unknown key gives one issue per key. The message text a library writes is its own and never travels in the body.
A contract can rename the code or reshape issues with a derived binding (Errors and natures). The members above are the defaults.
- Your stack: pick its page in the table above.
- Change the library: Switch the validator.