Describing File Uploads and Downloads Cleanly in OpenAPI
AI generated
{ }
GET
OpenAPI · REST API · File Handling · Documentation
Describing File Uploads and Downloads Cleanly in OpenAPI
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.

14 min read format: binary · multipart/form-data · encoding · Content-Disposition OpenAPI 3.1 · YAML · Swagger UI · Client Generation

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.

11. FAQ: Describing File Uploads and Downloads in OpenAPI

1format: binary vs. format: byte?
binary = raw binary data in multipart. byte = base64 in JSON. Confusing the two produces incorrect client code from generator tools.
2Describe an upload correctly in OpenAPI?
requestBody with multipart/form-data. File field: type: string, format: binary. encoding object for the allowed content type per part.
3Describe a download endpoint in OpenAPI?
Response with application/pdf (or octet-stream) and schema: {type: string, format: binary}. Content-Disposition in the headers object.
4What does the encoding object do?
Defines the content type per multipart part. Enables JSON metadata alongside binary data in the same request, clearly documented for integrators.
5Swagger UI shows a text field instead of a file picker?
format: binary is missing or the content type is not multipart/form-data. The correct combination automatically generates a file picker.
6Document Content-Disposition?
In the headers object of the response. Schema: type: string. A description with an example value. Mention filename* for accented characters per RFC 5987.
7Multiple files in a single request?
type: array, items: {type: string, format: binary} for an array field. Or separate fields with different names when different validation rules are needed.
8What does openapi-generator generate for format: binary?
TypeScript: Blob | File. Python: IO[bytes]. Java: File object. Direct passing possible without manual encoding.
9Describe different download formats?
Several content types under content in the response: JSON for metadata, PDF for binary content. Choice made via the Accept header.
10Filenames with accented characters in Content-Disposition?
filename*=UTF-8''Filename.pdf per RFC 5987. Plus filename= as an ASCII fallback. Both in the same header, explained in the OpenAPI description.