AlgoMaster Logo

Input Handling

Medium Priority16 min readUpdated September 13, 2026
Listen to this chapter
Unlock Audio

An API receives many kinds of input, and treating them all the same can create problems. The API should preserve legitimate text, such as a gift note containing an apostrophe. It should reject a request that repeats a quantity field if the repetition makes its meaning ambiguous. A small compressed body that expands into a large document needs processing limits. Input handling must account for these differences before the operation uses the data.

Input handling is the process of turning external data into values an operation can safely use.

This chapter follows that process from request acceptance through parsing, validation, authorization, and storage or execution. The examples use a fictional bookstore JSON API over HTTPS at api.bookstore.example.

1. Input Boundaries

External input includes more than a JSON body. Paths, query parameters, headers, cookies, uploaded files, and responses from other services can all influence behavior. Data the application reads from a database may also have originated with a caller and later reach a different operation.

A trust boundary is a point where data moves between components with different authority or assumptions. Passing through a browser form, gateway, or internal queue does not establish that a value is appropriate for every later use. Client validation improves feedback; server checks enforce the contract.

Separate the main responsibilities:

Scroll
ResponsibilityWhat it establishesBookstore example
Request acceptanceThe service can process this representation within configured limitsAccept a bounded JSON body
ParsingThe representation has a defined structureRead an object without duplicate properties
NormalizationDocumented equivalent forms have a chosen representationNormalize a display label if its contract requires it
ValidationValues satisfy the operation's rulesQuantity is an integer between 1 and 20
AuthorizationThis caller may perform the action on the referenced resourcesCustomer may access the selected draft
Safe useData stays data at its destinationBind the note as a SQL parameter

A valid identifier can point to another customer's record. A valid note can contain characters meaningful to HTML. Neither schema validation nor authentication resolves every boundary.

The diagram shows how a bounded request becomes an application command, an internal object containing only the values the operation needs:

This is a model of dependencies, not a mandatory middleware order. Authentication and coarse operation permissions can run before body parsing. Resource-specific authorization needs enough parsed input to identify the resource. Expensive processing and protected lookups should wait until earlier checks establish their prerequisites.

2. Bounded Request Acceptance

Decide which representations an endpoint accepts. For this bookstore's draft endpoint, the contract requires Content-Type: application/json and a UTF-8 body. JSON that systems exchange outside a closed ecosystem uses UTF-8. The endpoint rejects unsupported media types instead of trying several parsers until one accepts the bytes.

Do not select a parser based on whether a payload “looks like JSON.” Likewise, declaring a media type does not prove the content matches it. Check the supported media type, then let its parser validate the representation. An Accept header describes the response formats the client can receive; it does not identify the request body's format.

Set limits before allocating an unbounded body or constructing a large object tree. Useful limits include body bytes, header and query size, nesting depth, string length, object members, and array elements. Limit time spent receiving and processing input as well.

For illustration, a draft endpoint might accept at most 64 KiB of uncompressed request content and 50 items. These are separate limits: a single enormous note can exceed the byte budget, while 51 tiny items can exceed the item budget. A KiB is 1,024 bytes. The numbers need to match the actual product contract and expected traffic.

If the endpoint supports compressed request bodies, bound both received and decompressed bytes while processing. If compression is unnecessary, the endpoint can reject unsupported content codings. Never wait until decompression finishes to discover that the expanded body is too large.

A declared Content-Length can support an early rejection, but count actual bytes as they arrive. Let maintained HTTP servers handle message framing and reject conflicting framing metadata. Application code should not attempt to repair malformed HTTP messages.

3. Unambiguous Parsing

Security checks and execution must interpret input the same way. A parser differential occurs when components give different meanings to the same representation. An edge filter might approve one value while the application uses another.

Duplicate JSON Properties

The following is intentionally ambiguous input, not an accepted draft item:

JSON recommends unique object member names. Implementations differ when names repeat: some retain one value, some retain several, and some reject the document. This bookstore chooses to reject duplicates at every object level with 400 Bad Request.

A schema validator usually receives an already parsed object. If parsing discarded the first occurrence, validation cannot discover that duplication happened. Configure duplicate detection in the parser, before conversion into an ordinary map. Compare decoded property names, so "quantity" and "\u0071uantity" count as the same name.

The risk appears when components choose different occurrences:

Rejecting ambiguity at a shared boundary is easier to reason about than relying on undocumented first-value or last-value behavior throughout the stack.

Query Parameters and Locations

A query such as ?limit=10&limit=1000 also needs a policy. For a singleton parameter such as limit, reject repeated occurrences. For a documented collection parameter such as tag, preserve its values using the contract's array representation. HTTP does not impose one universal application policy for repeated query parameters.

Avoid merging path, query, and body values into one parameter map. If /order-drafts/draft_731 identifies the target, an unexpected body property named draft_id should not silently replace it. Read each value from its documented location.

Headers have their own field-specific combination rules. Do not apply a blanket “reject every repeated header” rule or a query-parameter merge strategy to HTTP fields.

Strict Syntax and Numeric Parsing

Reject malformed JSON and unwanted parser extensions. JSON has no NaN or Infinity literals, although some general-purpose libraries accept them by default. A syntactically valid number such as 1e400 can still exceed the numeric range the application supports.

Reject numeric values the application cannot represent within the field's defined range and precision. Do not round an identifier or price into a different accepted value. Strings generally work better for opaque IDs; monetary input needs a documented unit and exact numeric representation.

For a query parameter, parse the complete string using its defined grammar. A parser that reads 20items as 20 has accepted input the client did not actually send as an integer.

4. Explicit Input Contracts

Define accepted fields, types, ranges, and relationships before mapping data into application objects. An allowlist specifies what an operation accepts. A list of known bad strings cannot describe all invalid inputs, especially for international text.

Consider POST /order-drafts. It records a proposed order without charging money or reserving stock. Its input contract is:

Scroll
FieldAccepted valueAdditional rule
itemsRequired array1 through 50 entries; the API rejects repeated book IDs
items[].book_idRequired string1 through 64 characters from the bookstore's ASCII identifier alphabet
items[].quantityRequired JSON numberInteger value from 1 through 20
delivery_methodRequired stringExactly standard or express
gift_noteOptional stringAt most 200 Unicode code points; plain text

Here the identifier alphabet is ASCII letters, digits, and underscore. The format check does not establish that a book exists or is available to the caller. Gift notes preserve whitespace and punctuation; the contract disallows control characters except tab and line feed, and rejects unpaired Unicode surrogates. These text rules are endpoint choices.

An omitted gift_note means no note. An empty string is an accepted empty note. The API rejects null because it is not a string. Likewise, the API does not automatically convert quantity "2" to a number, and true must not become one item because a language treats booleans as integer-like values.

The quantity rule accepts 2 and 2.0 because both express an integer value. That is different from requiring a particular spelling in the original JSON bytes. Configure validation deliberately rather than inheriting accidental language behavior.

Reject unknown properties at the top level and inside each item. With JSON Schema, declaring properties alone does not prohibit extra fields; the schema must explicitly constrain them. Ensure the runtime validator enforces the chosen schema and its configuration. A schema file that no request passes through provides documentation but no protection.

Strict unknown-field rejection catches misspellings and attempts to submit server-managed fields. It also constrains compatibility: clients must not send new fields to servers that do not support them. If an API deliberately ignores unknown input for compatibility, it must still exclude that input from every command and persistence operation.

Do not bind the request directly to a database entity. Mass assignment occurs when broad automatic binding lets a caller change properties the operation never intended to expose. Build the draft command explicitly from the accepted fields, and obtain its owner and tenant from verified server context. Rejecting owner_id today should not depend on remembering to blacklist every future sensitive model property.

5. Normalization and Meaning

Normalization converts documented equivalent forms into a chosen representation. It is useful when the product considers those forms interchangeable. It is not permission to rewrite arbitrary input until validation passes.

For example, a bookstore may choose to trim a reading-list label and normalize its Unicode representation before checking its final length and uniqueness. A gift note may instead need exact whitespace preservation. Passwords, opaque identifiers, and signed content require their own rules; a generic string-cleaning function should not trim or lowercase them.

Limit the size of the original input, apply only the transformations the field's rules permit, and validate the value the operation will use. If normalization affects equality, the database's uniqueness rule and lookup logic must agree with it. Otherwise, two inputs can pass separate checks and collide when the database stores them.

Percent decoding is another source of changed meaning. Decode at the appropriate URI-processing boundary, and avoid decoding the same component repeatedly. Once a framework has decoded a path parameter, calling another decoder can turn previously literal text into a path separator or traversal sequence.

Normalization differs from sanitization, which removes or transforms content according to a safety policy. Silently deleting punctuation from a gift note changes customer data. For plain text, preserve permitted content and handle it safely where the application uses it. If a product intentionally accepts a subset of HTML, use a maintained HTML sanitizer with a defined policy rather than a homemade tag-removal expression.

6. Safe Use at the Destination

Input that passes validation is valid for a particular contract. It is not universally safe for SQL, HTML, a shell command, or a filesystem path. Injection occurs when one of these systems interprets data as instructions.

Suppose a permitted gift note says For Sam's birthday. Blocking apostrophes would damage ordinary use without fixing the underlying SQL construction problem. This intentionally flawed Python example inserts the note directly into SQL syntax:

With Python's SQLite interface, pass values separately using parameter binding. This focused fragment assumes the application has already checked the caller's permission to access the draft; transaction ownership belongs to the surrounding operation:

The database receives statement structure and data separately. Parameterization does not supply object authorization, and it does not generally bind table names, column names, or sort directions as identifiers. Map a public sort choice to a fixed server-owned SQL fragment rather than interpolating arbitrary input.

Other destinations need their own controls:

DestinationSafe handling approach
HTML textUse text APIs or template escaping appropriate to that context
JSON responseUse a JSON serializer rather than concatenating strings
Database query valuesUse the driver's parameter-binding interface
Operating-system processPrefer a library; otherwise use a fixed executable and argument array with argument validation
FilesystemResolve server-owned storage keys within a controlled location
Structured logsRecord bounded fields without raw credentials or uncontrolled message construction

Avoiding a shell removes shell parsing, but an external program may still interpret a supplied argument as an option. Validate the program's accepted arguments as well.

A note the application safely stores using SQL can still become unsafe when an administrative page inserts it as raw HTML. Output encoding represents data safely for the specific output context. Apply it when producing that output, rather than storing one supposedly safe escaped version for every future consumer.

The same note can travel through several contexts:

Each boundary preserves the text's role as data. Internal storage does not remove the need for safe handling at the next destination.

7. Structured and Binary Inputs

Do not handle every input with a string pattern. Use dedicated parsers for URLs, dates, and structured documents, followed by rules for the operation. A parseable URL does not establish that the server may fetch it; destination authorization remains necessary.

Regular expressions can help with small lexical rules, such as the bookstore's identifier alphabet. Some expressions take excessive time on carefully chosen input, a problem security engineers call regular expression denial of service. Bound input length before matching and use simple patterns or a parser with predictable resource behavior. Do not evaluate caller-supplied regular expressions unless the product explicitly supports them with suitable limits.

Files need checks on both metadata and content. An uploaded filename, extension, or Content-Type is a claim from the caller. For a cover-image upload, restrict accepted formats, verify content with an appropriate decoder, and bound decoded dimensions and processing work as well as compressed bytes.

Assign a server-generated storage name and keep uploaded content outside executable application locations. Preserve an original filename only as bounded display metadata. Avoid treating an upload name as a filesystem path. If the API supports archive extraction, constrain expanded size and entries, and prevent paths or links from escaping the destination directory.

Keep parsers updated and isolate risky processing where appropriate. A valid image still requires permission to attach it to a particular book. Format validation and resource authorization remain separate decisions.

8. A Complete Request Path

The draft endpoint accepts the following request. All tokens are nonfunctional placeholders. Each HTTP body's Content-Length counts the exact single-line UTF-8 JSON these examples show, without a trailing newline.

The service accepts the size and format, parses the object without duplicates, checks its fields, and verifies that book_219 is available to this customer. The punctuation is valid plain text. The service constructs a command with a verified owner and tenant, then stores the draft through parameterized database operations.

A response serializer can preserve the angle brackets in JSON text. A browser rendering the note must still treat it as text rather than inserting it as HTML.

Now the caller submits a numeric string:

Under this API's policy, syntactically valid JSON with invalid field values receives 422. The complete creation fails; the server saves no draft or item:

This uses Problem Details with an application-defined errors extension. Its location and code fields are bookstore conventions. The server reports the rule without reflecting the submitted body or exposing parser internals.

A correctly formed request from an authenticated caller lacking draft-creation permission instead receives an authorization denial:

Do not perform protected catalog lookups just to produce detailed input feedback for that caller. For callers who may create drafts, check referenced books within their authorized catalog view and avoid exposing whether an inaccessible identifier exists elsewhere.

Other failures stop at their relevant boundary: unsupported formats receive 415, malformed or ambiguous JSON receives 400, and excessive request content receives 413. These are this API's response choices within HTTP semantics; the 400/422 split is not universal.

State-dependent checks belong at the point where changes become durable. If a rule depends on mutable state, enforce it using the appropriate transaction or conditional write rather than trusting an earlier lookup. A catalog outage is a service failure, not evidence that a submitted book ID is invalid.

9. Verification at the Boundary

Verification should establish what the service accepts, what it rejects, and whether rejected requests cause effects. For this endpoint, the most useful cases target points where parsing or meaning can change:

CaseExpected outcome
Duplicate quantity, including an escaped equivalent nameReject before either value reaches execution
quantity as "2", true, null, or an out-of-range numberReject without coercion or partial creation
Repeated singleton query parameterReject before lookup or execution
Extra nested property or submitted owner_idReject; the caller cannot assign any server-owned field
Gift note with permitted Unicode and punctuationPreserve through storage and render as text
Body just beyond its byte limit or too deeply nestedStop within configured processing limits
Reference outside the caller's authorized viewDeny without disclosing protected record details
Dependency unavailable during a required checkFail without mislabeling the input as invalid

Exercise raw requests through the deployed parsing path, because tests that start with an already constructed object cannot expose duplicate keys or decoder disagreements. Include normal requests and boundary values so that rejecting everything does not appear to be a successful implementation.

Inspect database changes and downstream work in addition to status codes. Record bounded rejection categories and request identifiers for investigation, while excluding secrets and unnecessary payload contents. The objective is a predictable boundary whose behavior remains consistent as parsers, schemas, and storage models evolve.

Summary

Input handling starts before schema validation and continues after it. Bound processing, choose the expected parser, reject ambiguous representations, and define accepted fields and types explicitly. Normalize only according to the contract, construct commands from permitted values, and authorize referenced resources.

Validation establishes that a value fits an operation. Parameter binding, context-appropriate output handling, and controlled file or process interfaces keep that value safe at its destination. Verify both ordinary inputs and boundary cases, including the absence of unintended effects when a request fails.