{"title":"Request Formats","description":"JSON, forms, XML, deterministic CBOR, and encrypted COSE requests","section":"guides/schema-api","version":"v2","path":"guides/schema-api/parsers","canonical_url":"https://amberframework.org/docs/v2/guides/schema-api/parsers","markdown_url":"https://amberframework.org/docs/v2/guides/schema-api/parsers.md","inherited":false,"content_markdown":"# Request formats\n\n> **Released in `2.0.0-beta.5`:** bounded CBOR and bidirectional authenticated\n> COSE are available as opt-in request and response formats.\n\nDeclare every request representation an action actually accepts. Amber checks\nthe incoming `Content-Type` before parsing and returns 415 when the media type\nis outside the contract.\n\n## Where the examples go\n\n- Put `content_type` and field declarations inside a contract under\n  `src/schemas/`.\n- Put the COSE provider in `config/wire_format.cr` and require it from\n  `config/application.cr`.\n- The JSON, form, and XML documents shown here are HTTP request bodies sent to\n  the bound action; they are not files to add to the application.\n- Run key-generation and request commands from the application root beside\n  `shard.yml`.\n\n## Supported request media types\n\n| Media type | Parser |\n|---|---|\n| `application/json` or `text/json` | JSON object |\n| `application/xml`, `text/xml`, or `application/xhtml+xml` | XML document |\n| `application/x-www-form-urlencoded` | Form fields, including bracket notation |\n| `multipart/form-data` | Form fields and uploaded-file metadata |\n| `application/cbor` | Bounded deterministic CBOR object |\n| `application/cose` | COSE Encrypt0 containing the CBOR object |\n\nCSV, Protocol Buffers, and MessagePack are not built-in Amber V2 schema\nformats. Applications may integrate them separately, but public contracts\nshould not claim framework support that is not present.\n\n## Declare one or more formats\n\n**File: `src/schemas/create_pet_schema.cr`.**\n\n```crystal\nclass CreatePetSchema < Amber::Schema::Definition\n  content_type \"application/json\"\n\n  field :name, String, required: true\n  field :species, String, required: true\nend\n```\n\nTo support the same JSON-compatible object through JSON, CBOR, and encrypted\nCOSE:\n\n```crystal\ncontent_type \"application/json\", \"application/cbor\", \"application/cose\"\n```\n\nThe controller binding is unchanged. Amber chooses the request parser from\n`Content-Type` and the schema-aware response format from `Accept`.\n\n## JSON\n\n**File: the request body sent to the bound action — not a Crystal source file.**\n\n```json\n{\n  \"name\": \"Mochi\",\n  \"species\": \"cat\",\n  \"age\": 3,\n  \"tags\": [\"indoor\", \"friendly\"]\n}\n```\n\nThe top-level document must be an object. Malformed JSON and non-finite numbers\nfail before field validation.\n\n## URL-encoded forms\n\n**File: `src/schemas/registration_schema.cr`.**\n\n```crystal\nclass RegistrationSchema < Amber::Schema::Definition\n  content_type \"application/x-www-form-urlencoded\"\n\n  field :name, String, required: true\n  field :email, String, required: true, format: \"email\"\n  field :age, Int32, min: 13\n  field :tags, Array(String)\nend\n```\n\n**Example HTTP body:**\n\n```text\nname=Alex&email=alex%40example.com&age=28&tags[]=crystal&tags[]=amber\n```\n\nAmber reuses the router's cached form parse when method override or another\nrequest step has already inspected the body. It does not consume the form once\nfor routing and then hand an empty stream to the schema.\n\n## Multipart forms and files\n\n**File: `src/schemas/photo_upload_schema.cr`.**\n\n```crystal\nclass PhotoUploadSchema < Amber::Schema::Definition\n  content_type \"multipart/form-data\"\n\n  field :title, String, required: true\n  field :photo, Hash(String, JSON::Any),\n    required: true,\n    max_size: 5_000_000,\n    allowed_types: [\"image/jpeg\", \"image/png\", \"image/webp\"],\n    allowed_extensions: [\"jpg\", \"jpeg\", \"png\", \"webp\"]\nend\n```\n\nThe multipart parser exposes uploaded-file metadata to the schema and reuses\nAmber's cached multipart fields and files. Validation at this layer is an\nadmission check; use the [uploads guide](../uploads/) for storage ownership,\nimage processing, and serving policy.\n\n## XML\n\n**File: `src/schemas/create_event_schema.cr`.**\n\n```crystal\nclass CreateEventSchema < Amber::Schema::Definition\n  content_type \"application/xml\"\n\n  field :name, String, required: true\n  field :starts_at, Time, required: true\nend\n```\n\n**Example HTTP body:**\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<event>\n  <name>Amber meetup</name>\n  <starts_at>2026-09-01T18:00:00Z</starts_at>\n</event>\n```\n\nXML is available for inbound schema parsing. The schema-aware `respond_with`\nencoder currently emits JSON, CBOR, or COSE; do not declare automatic XML\nresponse encoding unless the controller implements and tests that response\npath explicitly.\n\n## Deterministic CBOR\n\n`application/cbor` carries the JSON-compatible contract in a compact binary\nform. Amber's decoder is bounded to:\n\n- 1 MiB per document;\n- 32 levels of nesting; and\n- 16,384 collection items.\n\nIt rejects indefinite lengths, duplicate map keys, invalid UTF-8, trailing\nbytes, byte strings where a JSON-compatible value is required, and non-finite\nnumbers. Typed schema validation runs after decoding exactly as it does for\nJSON.\n\n## Authenticated COSE Encrypt0\n\n`application/cose` carries that deterministic CBOR object in a tagged COSE\nEncrypt0 envelope using ChaCha20-Poly1305. Amber authenticates and decrypts the\nrequest, validates the object, then can encode, authenticate, and encrypt the\nresponse with a fresh 96-bit nonce.\n\nThere is no built-in development key.\n\n### 1. Generate a 32-byte deployment key\n\n**Run from: the application root.**\n\n```bash\nopenssl rand -base64 32\n```\n\nStore the result in the deployment secret manager as `AMBER_WIRE_KEY`. Store a\nnon-empty identifier such as `2026-08` as `AMBER_WIRE_KEY_ID`. Do not commit\neither value.\n\n### 2. Configure the provider\n\n**File: `config/wire_format.cr` — create this file.**\n\n```crystal\nAmber::Schema::COSE.configure(\n  Amber::Schema::COSE::KeyProvider.from_env!\n)\n```\n\n**File: `config/application.cr` — require it after Amber and before controller\nfiles.**\n\n```crystal\nrequire \"amber\"\nrequire \"./wire_format\"\nrequire \"../src/controllers/application_controller\"\nrequire \"../src/controllers/**\"\nrequire \"./routes\"\n```\n\nThe key provider selects keys by COSE key ID and can retain a grace key during\nrotation. A COSE request without configuration returns 503. Authentication,\nunknown-key, malformed-envelope, and replay-policy behavior should be covered\nby application tests before production use.\n\n## Response negotiation\n\nDeclare formats on the response schema too:\n\n```crystal\nclass PetResponseSchema < Amber::Schema::Definition\n  content_type \"application/json\", \"application/cbor\", \"application/cose\"\n\n  field :id, Int64, required: true\n  field :name, String, required: true\nend\n```\n\n- `Accept: application/json` returns JSON.\n- `Accept: application/cbor` returns deterministic CBOR.\n- `Accept: application/cose` returns authenticated COSE Encrypt0 containing\n  deterministic CBOR.\n- An undeclared representation returns 406.\n\nThe `X-Amber-Wire-Format` header describes Amber's selected COSE profile. It is\ninformational and never replaces client-side authentication of the message."}