Check different errors at the appropriate layer
On narrow screens, scroll the table sideways.
| Layer | Question | Example rejection |
|---|---|---|
| Transport and parser | Is the content readable within acceptable size and supported format? | Oversized body or unreadable JSON |
| Structure and types | Does the object match the agreed schema? | Missing required field, number supplied as text, unexpected property |
| Values and relationships | Do the values satisfy business rules? | End date before start date |
| Permission and state | May this caller perform this action now? | Another user's record or an already completed operation |
| Further processing | Is data used safely in its destination context? | Database statement assembled without safeguards |
OWASP separates syntactic from semantic validation and explicitly notes that input validation does not replace the primary defences against SQL injection or cross-site scripting. Data from partner services and internal integrations may also be unsuitable or manipulated. OWASP Input Validation.
Set limits before and during parsing
A schema is usually evaluated after the JSON object has been read. Its maxItems therefore does not automatically protect against the cost of initially receiving, decompressing and parsing a huge body. Depending on architecture, already limit body size, decompressed size, time and appropriate parser resources. Check which layer actually enforces each limit and how compressed content is handled.
Accept only needed media types. A JSON API need not automatically accept XML, native object serialisation or every representation supported by a library. Deserialisation that creates arbitrary application classes or executable constructs from external data needs particularly narrow controls. Ordinary JSON data objects avoid some such mechanisms but do not eliminate size, value or permission problems. OWASP Deserialization.
Duplicate property names deserve an explicit parser rule: some JSON parsers retain the last value, others reject them. If an upstream check and the application decide differently, they may assess different data. RFC 8259 recommends unique object names. Rejecting duplicates is a suitable contract decision of your own; a subsequent schema may never see lost duplicates. RFC 8259, section 4.
A small schema for a clearly bounded task
The example describes a fictional internal appointment booking. Title, seat count and two dates are required. The limits of 120 characters and 20 seats are deliberate example choices, not universal security values. The environment uses JSON Schema Draft 2020-12.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 120 },
"seats": { "type": "integer", "minimum": 1, "maximum": 20 },
"start_date": { "type": "string", "format": "date" },
"end_date": { "type": "string", "format": "date" }
},
"required": ["title", "seats", "start_date", "end_date"],
"additionalProperties": false
}
properties alone does not make a field mandatory; required does that here. additionalProperties: false excludes extra properties for this simple object. This does not automatically apply to every nested object or schema composition. Avoiding mass assignment explains permitted fields.
Check your validator's options
In Draft 2020-12, format is initially an annotation in the standard dialect. Depending on validator and configuration, a date is therefore not automatically validated as a date. The specification explicitly distinguishes format annotation from format assertion. Specify dialect, library version and format checks used; test them with a deliberately invalid value. JSON Schema Validation, section 7.
The following example uses the Python library jsonschema with an explicitly enabled FormatChecker. schema is the JSON from the preceding block loaded as a Python object. The additional business date rule follows schema validation.
from datetime import date
from jsonschema import Draft202012Validator, FormatChecker
validator = Draft202012Validator(schema, format_checker=FormatChecker())
def validate_booking(data):
validator.validate(data)
start = date.fromisoformat(data["start_date"])
end = date.fromisoformat(data["end_date"])
if end < start:
raise ValueError("End date is before start date")
return data
This example has been checked locally with valid data, incorrect types, extra fields, boundary values, an invalid date and reversed date order. It makes no booking. Availability, user permissions, concurrent bookings, parser limits and HTTP error handling are not implemented. They remain separate tasks.
Type declarations in application code do not prove runtime validation of external data either. TypeScript annotations, for example, are erased during compilation; incoming JSON still needs actual validation. Use a maintained tool suited to the concrete format instead of writing a new complex regular expression for everything. TypeScript: Erased Types.
Valid data still needs safe use
A correctly formatted date may lie outside the permitted booking window. An existing customer ID may belong to another organisation. A formally valid amount may need to be determined by the server rather than the client. These rules belong in the responsible application layer and must be checked against current data.
Use parameterised queries or the corresponding safe database-driver API for database values. Do not construct statements by concatenating request text. Dynamic column or sort identifiers that cannot be bound as ordinary values need a fixed mapping of allowed options. Output into HTML or other interpretation contexts requires suitable output encoding; globally removing a few special characters does not solve this.
Likewise, format: uri does not authorise fetching a URL. Destination selection and outbound access need separate limits; see preventing SSRF. Schema references themselves should come from controlled sources. Do not let an external schema file dictate unlimited network resources or computationally expensive checks.
Introduce data-contract changes in a controlled way
Before tightening rules, collect representative synthetic examples from legitimate clients. In particular, test empty values, null, omitted fields and numeric strings. Automatic conversion can offer compatibility, but must explicitly belong to the contract and be unambiguous before the business decision.
For rejected input, document the affected public field and a stable reason. Do not return complete request bodies, tokens or internal error traces. The error reference helps with 400, 413, 415, 422 and machine-readable details. A faulty request must not leave unwanted partial changes.
If a narrower schema blocks existing clients, correct the contract specifically or use a previously tested compatible validator. Rollback should preserve size and permission boundaries. The public API Security Check does not verify request schemas, deserialisers or business booking rules. Testing your own API security covers your evidence; reading HTTP findings explains public content-type and method observations.
Sources and date
Sources checked: 27 September 2026. Links also appear alongside the statements they support.