{"title":"Amber V2 Schemas Are Contracts the Framework Actually Enforces","summary":"The next V2 beta keeps legacy validation working while adding automatic typed request and response contracts, OpenAPI 3.1, CBOR, COSE, and measured costs on a $4 server.","date":"2026-08-13","category":"Release","author":"Seth Tucker","url":"https://amberframework.org/blog/2026/08/13/amber-v2-executable-schema-contracts","markdown_url":"https://amberframework.org/blog/2026/08/13/amber-v2-executable-schema-contracts.md","json_url":"https://amberframework.org/blog/2026/08/13/amber-v2-executable-schema-contracts.json","image_url":"https://amberframework.org/assets/characters/amber-hero-original-studio-9664a92e5af8767af84ceab3f1c79f34.webp","content_markdown":"# Amber V2 schemas are contracts the framework actually enforces\n\nThere is an easy trap in API frameworks: you write a beautiful schema, use it\nto generate documentation, and then discover that the real request path never\nactually ran it.\n\nWe found that gap in Amber's V2 beta work. The Schema API had good ideas and a\nlot of surface area, but declaring a schema did not yet guarantee that a\nrequest would succeed or fail because of it. That is exactly the kind of thing\na beta is supposed to expose before people build production assumptions on\ntop of it.\n\nAmber `2.0.0-beta.5` closes that gap. The reviewed work landed in\n[Amber PR #1408](https://github.com/amberframework/amber/pull/1408), and the\npublished prerelease tag points to that canonical merge commit.\n\n## The compatibility promise comes first\n\nIf an Amber 1.x application uses `params.validation`, upgrading the framework\nshould still feel like an upgrade. That API is deprecated, but it remains\nfunctional in V2. We do not plan to remove it before a later V2 minor such as\n2.5, and the exact removal release will be announced separately.\n\nThat means an application can change its Amber version, run its tests, build,\nand ship the stability improvements before rewriting validation code. New\nschemas can then be adopted one action at a time. A deprecation warning is a\nmigration prompt, not a demand to stop the upgrade and rebuild every\ncontroller.\n\n## One contract now owns the whole boundary\n\nPut a reusable request contract under `src/schemas/`:\n\n```crystal\nclass CreatePetSchema < Amber::Schema::Definition\n  content_type \"application/json\"\n  additional_properties false\n\n  field :name, String, required: true, min_length: 1, max_length: 80\n  field :species, String, required: true, enum: [\"cat\", \"dog\", \"other\"]\n  field :age, Int32, min: 0, max: 50\nend\n```\n\nBind it to the action in `src/controllers/pets_controller.cr`:\n\n```crystal\nclass PetsController < ApplicationController\n  schema :create, CreatePetSchema\n\n  def create\n    input = validated_as(CreatePetSchema)\n    Pet.create!(\n      name: input.name.not_nil!,\n      species: input.species.not_nil!,\n      age: input.age\n    )\n  end\nend\n```\n\nThe ordinary route stays ordinary:\n\n```crystal\n# config/routes.cr\npost \"/pets\", PetsController, :create\n```\n\nBefore `create` runs, Amber checks the media type, parses the body, collects\ndeclared path, query, header, and cookie values, coerces each value, and applies\nthe schema. Invalid input stops there. The action receives a request-local\ntyped object, not a mutable schema instance shared by concurrent requests.\n\nA `response_schema` declaration applies the same discipline on the way out.\nApplication code that produces the wrong shape or status fails as a 500\ncontract error instead of silently serving a response that disagrees with its\ndocumentation.\n\nThe same enforced declarations generate OpenAPI 3.1. They carry body fields,\nreal path/query/header/cookie parameters, nested schemas, paired fields,\nalternatives, conditional requirements, content types, and response status.\nThere is no second registry for the documentation to drift away in.\n\n## JSON, compact CBOR, or encrypted COSE\n\nThe contract can accept and return `application/json` or deterministic,\nbounded `application/cbor`. An application that owns both ends of the wire can\nalso choose `application/cose`: a COSE Encrypt0 envelope containing that CBOR\nobject and authenticated with ChaCha20-Poly1305.\n\nCOSE works in both directions. Amber authenticates and decrypts the incoming\nmessage before validation, then can validate, encode, authenticate, and encrypt\nthe response with a fresh nonce. There is intentionally no built-in\ndevelopment key; an application must supply a 32-byte deployment secret and\nkey ID.\n\nCSV, Protocol Buffers, and MessagePack are not built-in formats. We found old\ndraft documentation that said they were and removed it. Release documentation\nshould describe the framework people can actually run, not the framework a\ndraft once imagined.\n\n## We measured what enforcement costs\n\nThe performance question is not whether an isolated route matcher can move\nmillions of strings. It is what happens to complete HTTP requests when Amber\nparses, routes, validates, runs the action, validates the response, and writes\nthe result.\n\nOn August 13, 2026, the exact $4 DigitalOcean target—one shared vCPU and 512 MB\nadvertised memory—ran four versions of the same eight-field request and\nfive-field acknowledgement. A separate four-dedicated-vCPU machine generated\nload over an isolated private VPC. Each scenario had a warmup and seven\nrotating 15-second measurements with 16 persistent connections.\n\n| Complete HTTP scenario | Median requests/s | Observed range | Median p50 | Median p99 |\n| --- | ---: | ---: | ---: | ---: |\n| Generic JSON without schemas | 20,728 | 19,019–22,618 | 0.688 ms | 2.813 ms |\n| Validated JSON | **19,488** | 18,573–22,236 | 0.721 ms | 3.185 ms |\n| Validated CBOR | **21,742** | 19,904–23,256 | 0.646 ms | 2.921 ms |\n| Bidirectional authenticated COSE | **14,443** | 10,962–15,160 | 1.006 ms | 3.612 ms |\n\nIn this workload, request and response enforcement cost 6.0% compared with\ngeneric JSON decoding. CBOR was 11.6% faster than validated JSON and reduced\nthe request body from 257 bytes to 223 bytes. Bidirectional COSE was 25.9%\nslower than validated JSON because it did the cryptographic work on both the\nrequest and response.\n\nAll **7,974,608** measured requests returned HTTP 200. No slower trial was\ndiscarded as an outlier, and the Amber process peaked at 16.6 MiB on the 512 MB\ntarget.\n\n## The request path improved too\n\nThe previous round used the same cloud plans, region, reported processor\nmodels, 1,002-route workload, payloads, load tool, connection count, rotation,\nand measurement duration. After integrating the optimized span router and the\nassociated request, params, pipeline, and responder allocation work, the\nmedians improved by 49.2% for generic JSON, 50.1% for validated JSON, 46.1% for\nvalidated CBOR, and 39.1% for bidirectional COSE.\n\nThat is the complete integrated patch set—not proof that the router alone\ncaused every percentage point. The rounds also ran on separate ephemeral\nDroplets, so ordinary cloud-host variation remains a limitation.\n\n## Keep the workload attached to the number\n\nThis was a synthetic in-memory acknowledgement endpoint. It did not include a\ndatabase, external service, TLS termination, HTML rendering, application\nlogging middleware, or public-internet latency. It establishes the measured\ncost of codecs, validation, and the framework request path on constrained\nhardware. It is not a production capacity promise.\n\nThe Amber website's roughly 5,900 complete homepage responses per second is a\ndifferent, application-shaped workload and remains the better headline for\nwhat a real rendered site did. The 19,488 validated JSON result answers a more\nspecific question: can Amber make request and response contracts real without\ngiving up the performance people came to Crystal for? In this measured\nworkload, yes.\n\nStart with the [request and response schema guide](/docs/v2/guides/schema-api/),\nread the [full performance methodology](/docs/v2/guides/performance/), or\ninspect the [machine-readable summary](/benchmarks/amber-v2-schema-contract-round27-summary.json)\nwhen you need the exact retained values.\n"}