Documentation

Request and Response Schemas

New
Browse documentation

Published 2026-08-13. V2 is a prerelease; the web core is release-gated and other previews are named separately. What beta means.

Read this page as HTML, Markdown, or structured JSON—or open the published Markdown with an AI assistant. Gemini receives the prompt through your clipboard because its signed-out page does not reliably prefill URL text; paste when the new tab opens. External assistants need the public site URL.

1.4.1
Unavailable in this version
1.5
Unavailable in this version

Request and response schemas

Released in 2.0.0-beta.5: the framework now enforces these request and response contracts automatically. Amber CLI 2.0.6 generates controllers that use this path by default.

Amber V2 schemas are executable controller contracts. One declaration controls request parsing, validation, typed values, response validation, content negotiation, and OpenAPI output. A declared controller schema runs automatically before the action; it cannot become documentation that the application forgets to enforce.

The V1 params.validation API remains functional in 2.0.0-beta.5, but it is deprecated. Amber plans to keep it throughout the initial V2 compatibility window and remove it no earlier than a later minor release such as 2.5. The exact removal release will be announced separately. Upgrade the framework first, then migrate one action at a time.

Build a complete JSON endpoint

This example creates a pet through POST /pets. Every block names the file where it belongs.

1. Define the request and response contracts

File: src/schemas/pet_schemas.cr — create this file.

Crystal
class CreatePetSchema < Amber::Schema::Definition
  content_type "application/json"
  additional_properties false

  field :name, String, required: true, min_length: 1, max_length: 80
  field :species, String, required: true, enum: ["cat", "dog", "other"]
  field :age, Int32, min: 0, max: 50
  field :request_id, String,
    required: true,
    source: Amber::Schema::ParamSource::Header,
    source_name: "X-Request-ID"
end

class PetResponseSchema < Amber::Schema::Definition
  content_type "application/json"
  additional_properties false

  field :id, Int64, required: true
  field :name, String, required: true
  field :species, String, required: true
  field :age, Int32
end

additional_properties false closes the contract. An undeclared request-body or response field then fails validation. Omit that line while an existing API must continue accepting and carrying fields that are not yet declared.

2. Bind both contracts to the controller action

File: src/controllers/pets_controller.cr — add the declarations above the action and the action inside PetsController.

Crystal
require "../schemas/pet_schemas"

class PetsController < ApplicationController
  schema :create, CreatePetSchema
  response_schema :create,
    PetResponseSchema,
    status: 201,
    description: "Pet created"

  def create
    input = validated_as(CreatePetSchema)
    pet = Pet.create!(
      name: input.name.not_nil!,
      species: input.species.not_nil!,
      age: input.age
    )

    payload = {
      "id"      => JSON::Any.new(pet.id),
      "name"    => JSON::Any.new(pet.name),
      "species" => JSON::Any.new(pet.species),
    }
    payload["age"] = JSON::Any.new(pet.age.not_nil!) if pet.age

    respond_with(payload, status: 201)
  end
end

Amber parses and validates the request before create runs. validated_as returns the request-local schema instance and its generated typed getters. Use validated_params when a Hash(String, JSON::Any) is more convenient.

respond_with validates the response object and status before writing bytes. If application code produces a shape or status outside PetResponseSchema, Amber returns a 500 contract error instead of silently serving an undocumented response.

3. Add the route

File: config/routes.cr — add this inside the existing router block.

Crystal
post "/pets", PetsController, :create

Schemas bind to controller actions, not to extra route keywords. The ordinary route also supplies the metadata used by OpenAPI generation.

4. Exercise the contract

Run from: the application root while amber watch is running.

Terminal
curl --fail-with-body \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'X-Request-ID: guide-1' \
  --data '{"name":"Mochi","species":"cat","age":3}' \
  http://127.0.0.1:3000/pets

Automatic contract responses

Status Meaning
400 The JSON, CBOR, or COSE body is malformed.
406 The requested response media type is not declared by the response schema.
415 The request Content-Type is not declared by the request schema.
422 The document parsed, but its values do not satisfy the schema.
500 Application code produced a response shape or status outside its declared contract.
503 A COSE request arrived before a key provider was configured.

Actions without a declared schema retain their existing params behavior.

Continue from here

  • Schema basics covers field types, constraints, parameter sources, nested objects, and conditional relationships.
  • Validation and migration explains enforcement, response contracts, and the deprecated-validator compatibility bridge.
  • Request formats covers JSON, forms, XML, CBOR, and encrypted COSE.
  • OpenAPI serves an OpenAPI 3.1 document from the same registered controller contracts.