Using format: binary, multipart/form-data and Content-Disposition correctly
OpenAPI specifications for file operations frequently contain errors: misused format values, missing encoding objects and undocumented response headers for downloads. This article shows how to model uploads and downloads precisely in OpenAPI 3.1, so that client generators produce correct code and integrators encounter no surprises.
Table of Contents
- 1. OpenAPI and Binary Data: The Basics
- 2. format: binary vs. format: byte, the critical difference
- 3. Fully modeling multipart/form-data
- 4. The encoding object for mixed content types
- 5. Describing download endpoints correctly
- 6. Content-Disposition and filenames in the response header
- 7. Describing multiple files in a single request
- 8. Client generation with format: binary
- 9. Upload modeling approaches compared
- 10. Summary
- 11. FAQ
1. OpenAPI and Binary Data: The Basics
OpenAPI describes HTTP interfaces, and HTTP can transport arbitrary binary data. The specification therefore needs a way to distinguish between plain text values, base64-encoded binary data and raw file content. In OpenAPI 3.x this is handled through the format field in the schema: string is the base type, and format: binary and format: byte are two different interpretations of it. Anyone who does not know the difference will document file endpoints incorrectly, with the result that generated clients produce unusable code and manual integrators pick the wrong encoding.
The second foundational aspect: OpenAPI describes file uploads and downloads asymmetrically. For an upload, you describe the requestBody with the correct content type and schema. For a download, you describe the responses entries with the content type of the returned file and the schema of the binary data. Both sides require different YAML structures, and this is exactly where most documentation errors occur in practice: developers copy upload structures for downloads, or confuse where the schema object needs to be placed.
2. format: binary vs. format: byte, the critical difference
format: byte describes base64-encoded binary data that is transmitted as a normal JSON string. This makes sense when a small binary file (for example a small image or a certificate) needs to be transmitted as part of a JSON object. The client encodes the binary data as base64, embeds the string in the JSON and sends Content-Type: application/json. The file is decoded upon receipt. This method increases the payload size by about 33 percent due to the base64 encoding.
format: binary, on the other hand, describes raw binary data that is not JSON-encoded. This format hint is used exclusively in the context of multipart/form-data or application/octet-stream. Client generators interpret format: binary as a signal to send the file as a file part, not as a string. Anyone who uses format: binary inside a normal JSON schema without a multipart context produces an inconsistent specification: JSON cannot contain binary data directly, and tools will handle this inconsistently.
# openapi/schemas/file-schemas.yaml
components:
schemas:
# WRONG: binary inside regular JSON request, JSON cannot contain raw binary
BadUploadRequest:
type: object
properties:
file:
type: string
format: binary # incorrect: binary outside multipart context
name:
type: string
# CORRECT: base64 encoding for embedding binary in JSON
EmbeddedFileRequest:
type: object
required: [fileData, filename]
properties:
fileData:
type: string
format: byte # base64-encoded, suitable inside JSON
description: Base64-encoded file content (max 1 MB due to size overhead)
filename:
type: string
description: Original filename for storage
mimeType:
type: string
description: MIME type of the encoded file
# CORRECT: binary used in multipart/form-data context (see requestBody)
DocumentUploadResponse:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
size:
type: integer
description: File size in bytes
3. Fully modeling multipart/form-data
A fully modeled multipart/form-data endpoint in OpenAPI 3.1 uses the requestBody object with a content key for the MIME type multipart/form-data. The schema object underneath describes all form fields as a normal JSON schema: text fields as type: string, numeric fields as type: integer, and file fields as type: string, format: binary. The required array at the schema level specifies which fields must be present. This structure is the only way to describe upload endpoints so that Swagger UI generates a functioning test form and OpenAPI client generators produce correct multipart requests.
A common mistake is omitting the required array in the schema; without it all fields are optional, and integrators only find out at runtime which fields are actually mandatory. A second mistake: if the API accepts several different file types (for example an image or a PDF, but not both at once), this is modeled with oneOf at the schema level, not through separate endpoints. OpenAPI 3.1 has full JSON Schema support here and allows oneOf, anyOf and discriminator in requestBody schemas as well.
# openapi/paths/upload-complete.yaml
/api/v1/documents:
post:
operationId: uploadDocument
summary: Upload document with metadata
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
- title
properties:
file:
type: string
format: binary
description: Document file (PDF or image, max 20 MB)
title:
type: string
minLength: 3
maxLength: 200
description: Human-readable document title
tags:
type: array
items:
type: string
maxItems: 10
description: Optional classification tags
expiresAt:
type: string
format: date
description: Optional expiry date (ISO 8601)
encoding:
file:
contentType: application/pdf, image/jpeg, image/png, image/webp
responses:
'201':
description: Document created
headers:
Location:
schema:
type: string
format: uri
description: URL of the newly created document resource
content:
application/json:
schema:
$ref: '#/components/schemas/Document'
4. The encoding object for mixed content types
The encoding object in requestBody allows the content type to be specified for individual fields of a multipart request. This is especially important for the file field: if the API only accepts PDFs, encoding.file.contentType: application/pdf should be set, so tools and integrators know that other types are rejected. If headers for individual parts need to be set as well, for example a specific character set for a text field, these can be defined in the encoding.field.headers object.
An advanced scenario: a multipart request contains a binary part and a JSON part. Without an explicit encoding object it is unclear how the JSON part is encoded. With encoding.metadata.contentType: application/json it is defined that this field is itself encoded as a JSON string within the multipart body. The receiving server then needs to parse this part as JSON. This pattern allows structured metadata to travel alongside binary data in a single network round trip, without having to flatten the metadata into separate string form fields.
5. Describing download endpoints correctly
Download endpoints return binary data, which is described in OpenAPI through the responses section. The correct content type for generic file downloads is application/octet-stream. If the API always returns a specific type (for example always PDF), application/pdf should be used instead, since it is more precise and lets the client choose the correct decoder. The schema of the response body is {type: string, format: binary} in both cases. This combination signals to OpenAPI tools that the response body contains raw binary data.
An important addition for download endpoints: if the API can deliver both JSON metadata and the binary content (for example based on the Accept header), both content types need to be described under content. content: application/json: schema: DocumentMetadata for the metadata variant and content: application/pdf: schema: {type: string, format: binary} for the binary variant. Swagger UI then shows both variants, and integrators know they can choose between JSON and binary via the Accept header.
6. Content-Disposition and filenames in the response header
The Content-Disposition header controls whether the browser displays a file inline or saves it as a download. Content-Disposition: attachment; filename="report-2026-05.pdf" triggers a download dialog. Content-Disposition: inline displays the file in the browser when the content type is supported. In OpenAPI this header is described in the headers object of the response. The schema of the header is a string, and a regular expression can be given as a pattern describing the allowed syntax of the header value. Since the filename is variable, a simple description field with an example value is often sufficient.
An important detail for international filenames: the plain filename parameter does not support non-ASCII characters. For filenames with accented characters or other special characters, filename* per RFC 5987 must be used: Content-Disposition: attachment; filename*=UTF-8''Report%202026-05.pdf. APIs that need to return files under their original, user-supplied names should set both parameters: filename as an ASCII fallback and filename* for correct rendering in modern clients. This behavior should be explained explicitly in the header description within the OpenAPI documentation.
# openapi/paths/download.yaml
/api/v1/documents/{id}/download:
get:
operationId: downloadDocument
summary: Download a document by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Document content
headers:
Content-Disposition:
description: >
Attachment with original filename.
Uses filename* (RFC 5987) for non-ASCII names.
Example: attachment; filename*=UTF-8''Report%202026.pdf
schema:
type: string
Content-Length:
schema:
type: integer
description: File size in bytes
ETag:
schema:
type: string
description: Unique file version identifier for caching
content:
application/pdf:
schema:
type: string
format: binary
application/octet-stream:
schema:
type: string
format: binary
'404':
$ref: '#/components/responses/NotFound'
7. Describing multiple files in a single request
When an API accepts multiple files in a single request, OpenAPI offers two modeling approaches. The first: define a form field as an array, type: array, items: {type: string, format: binary}. This matches the HTML pattern <input type="file" multiple> and sends multiple parts under the same field name. The second approach: separate field names for each file, primaryDocument: {type: string, format: binary} and attachments: {type: string, format: binary}. This approach allows different validation rules per file slot.
The maxItems constraint on the array field limits the number of uploadable files in the documentation. On the server side this limit must be implemented separately, since OpenAPI constraints are purely documentary and are not enforced automatically. A common mistake: when the server only accepts a single file per request, the array pattern is used anyway because items: {type: string, format: binary} "obviously looks correct". This should be a plain single field, not an array, otherwise integrators end up choosing unnecessarily complex client implementations.
8. Client generation with format: binary
OpenAPI client generators such as openapi-generator and swagger-codegen process format: binary differently depending on the target language. In TypeScript, a field with format: binary is generated as Blob | File. In Python as IO[bytes]. In Java as a File object. These types let the client pass filesystem objects directly, without manual encoding. If the schema is faulty, for example format: binary is missing or has been replaced with format: byte, the client generates a string parameter instead, and the integrator has to base64-encode the file manually.
For Symfony backends it is worth testing the generated client library regularly: the generated TypeScript client method should accept a File object directly from the browser FileReader. If that is not the case, there is an error in the OpenAPI specification. A practical test: open Swagger UI for your own endpoint and try the file upload field. Swagger UI generates a natural file picker from a correct specification. If a plain text input field appears instead, format: binary is missing or the context is wrong.
| Scenario | Content-Type | Schema | Note |
|---|---|---|---|
| File upload | multipart/form-data | format: binary | File as a part, not base64 |
| Small image in JSON | application/json | format: byte | base64, +33% size |
| Generic download | application/octet-stream | format: binary | Raw binary data in the body |
| PDF download | application/pdf | format: binary | Specific MIME type preferred |
| Multiple files | multipart/form-data | array, items: binary | Same field name, multiple parts |
Mironsoft
OpenAPI documentation, REST API design and client generation
OpenAPI specifications that client generators can actually use?
We review and fix OpenAPI specifications for file endpoints, fully document upload and download variants, and validate the generated clients against the implementation.
Specification review
Check the OpenAPI document for errors in format, encoding and response headers
Client generation
Generate TypeScript and PHP clients from OpenAPI and validate them against the API
Documentation
Fully document upload and download endpoints along with all their error cases
10. Summary
Describing file uploads and downloads correctly in OpenAPI 3.1 requires understanding a few but critical distinctions: format: binary for raw binary data in a multipart context, format: byte for base64-encoded data in JSON. The encoding object specifies which MIME types are accepted for individual form parts. Download endpoints describe the response with the most specific available MIME type and the Content-Disposition header in the headers section. For international filenames, both filename and filename* (RFC 5987) must be set.
The practical test for a correct specification: Swagger UI shows a natural file picker for upload fields, and a generated TypeScript client accepts a File object directly as a parameter. If a plain text input field appears instead, or the client expects a string parameter, there is an error in the specification. The YAML structures shown here are the direct path to OpenAPI documentation that is genuinely usable both by integrators and by code generators.
File Uploads and Downloads in OpenAPI, the essentials at a glance
format: binary vs. byte
binary = raw binary data in multipart/form-data. byte = base64-encoded in JSON. Confusing the two produces incorrect client code generation.
encoding object
Defines the content type per multipart part. Enables JSON metadata alongside binary data in the same request.
Download response
application/pdf (or octet-stream) plus schema: {type: string, format: binary}. Document Content-Disposition in the headers object.
Validation test
Swagger UI shows a file picker. The generated client accepts a File/Blob object. A text field means an error in the specification.