JSON vs Form Data vs Multipart: Send the Right HTTP Request with cURL

Two HTTP requests can use the same URL and method yet produce very different results. One sends a JSON object. Another sends form fields separated by &. A third sends a file upload with a multipart boundary. If the server expects one format and receives another, the application may reject the request or quietly parse the wrong values.

This tutorial compares three common request body formats and shows how to construct each with cURL. Along the way, you can use the free Web2Generators cURL command line builder for JSON and literal bodies, and the URL encoder and decoder to inspect form values.

Start with the difference between a URL and a body

A URL may contain a query string:

https://example.com/api/items?category=books&page=2

Those values are part of the request target. A POST request can also include a body containing fields or structured data. Both can appear in a single request, but they have different roles. An API might use query parameters for pagination and a JSON body for a new item’s details.

The Content-Type header tells the recipient how to interpret the body. Another header, Accept, tells the server which response representation the client would prefer. The two are not interchangeable.

FormatTypical Content-TypeTypical use
JSONapplication/jsonAPI objects and arrays
URL-encoded formapplication/x-www-form-urlencodedSimple HTML form fields
Multipartmultipart/form-data; boundary=...File uploads and mixed fields

The correct choice comes from the endpoint’s contract, not from which cURL option is shortest.

1. Sending a JSON body

A JSON API often expects an object such as:

{
  "name": "Example notebook",
  "quantity": 2,
  "categories": ["paper", "office"]
}

Send it with an explicit content type:

curl \
  --request POST \
  --url 'https://example.com/api/items' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data-raw '{"name":"Example notebook","quantity":2,"categories":["paper","office"]}'

These example endpoints are illustrative and may not exist. Replace them with an API you own or are authorized to test.

--data-raw transmits the literal text. It does not validate that the body is valid JSON. In a Bash-like shell, single quotes around the JSON make ordinary double-quoted JSON keys easier to write. PowerShell and Windows Command Prompt have different quoting behavior, and the builder does not generate commands for those shells. Its default multiline output works in POSIX shells such as Bash or Zsh; single-line output requires Bash or Zsh.

With cURL 7.82.0 or later, --json is a convenient alternative:

curl --json '{"name":"Example notebook","quantity":2}' \
  'https://example.com/api/items'

This option supplies JSON-related headers and posts the supplied data, but it still does not validate the JSON structure. If your server responds with a validation error, compare field names, types, and required properties before blaming HTTP transport.

2. Sending URL-encoded form fields

Traditional HTML forms commonly use application/x-www-form-urlencoded. Instead of JSON syntax, the body looks like a query string:

q=C%2B%2B+tips&tag=programming&tag=beginner

After form-style decoding, the search term is C++ tips. The %2B sequences protect literal plus signs; the unescaped + stands for a space. Two tag fields are present, not one. Whether the server keeps both values depends on its parser and API contract.

A hand-built request might look like this:

curl \
  --request POST \
  --url 'https://example.com/search' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw 'q=C%2B%2B+tips&tag=programming&tag=beginner'

When constructing the request directly with cURL, --data-urlencode can encode each value for you:

curl \
  --url 'https://example.com/search' \
  --data-urlencode 'q=C++ tips' \
  --data-urlencode 'tag=programming' \
  --data-urlencode 'tag=beginner'

Without --get, these options send a POST body by default. cURL also sets the usual form content type for data options if you do not override it. If you do want query parameters on a GET request, add --get. With the name=value syntax, cURL encodes the value, so field names containing special characters must already be encoded.

This is a good place to use the URL encoder, decoder, and query editor. Choose Form value to see how spaces, literal plus signs, ampersands, and non-ASCII text are encoded. The site’s post Meet the New URL Encoder, Decoder, and Query Editor also explains why repeated keys matter.

3. Uploading a file with multipart data

Use multipart/form-data when the endpoint expects an uploaded file as a form field. Some APIs instead expect the file bytes as the entire body; follow the endpoint’s documentation. A multipart request has individually delimited parts, each with its own headers. It is not simply a long URL-encoded string, and it is not a JSON document with a filename pasted into it.

Use --form for a file field:

curl \
  --url 'https://example.com/api/uploads' \
  --form-string 'description=Test document' \
  --form 'document=@./demo.txt;type=text/plain'

The --form-string option keeps the description literal, including any leading @ or <. In the file field, @ tells cURL to read the local file ./demo.txt. Create a harmless test file first and verify the path. Unlike JSON and form data sent as literal text, the file contents become a separate multipart part.

Let cURL generate the multipart body and its Content-Type header. The header’s boundary must match the delimiters in the body. With --form, cURL handles both; you do not need to construct either manually. Setting a multipart header on a literal text body does not turn it into a file upload.

The Web2Generators builder currently generates literal bodies with --data-raw; it does not offer multipart file fields. Prepare this upload command in your terminal using the example above.

Be careful with copied cURL examples from browser developer tools. They may include session cookies, bearer tokens, or other sensitive values. Remove or replace those before pasting commands into issue trackers or public documentation.

Why the server returns 400, 415, or 422

A request can reach the correct endpoint but still fail. Some common patterns are:

SymptomPossible causeFirst check
400 Bad RequestMalformed syntax or an invalid requestIs the JSON valid? Are form delimiters correct?
415 Unsupported Media TypeBody format is not supported by the endpointDoes Content-Type match the API contract?
422 Unprocessable ContentSyntax is understood, but values do not meet the rulesCheck required fields, types, and validation messages.
Fields are missingServer parsed a different formatCompare raw body and parser configuration.
File field is emptyMultipart field name or boundary is wrongCheck --form, field name, and file path.

Status codes are clues, not universal diagnoses. The API’s own documentation and response body are the best sources for its validation rules. Also verify whether a redirect changes the request behavior before it reaches the final endpoint.

Compare requests in the browser and terminal

Suppose a form succeeds in the browser but fails when reproduced in a terminal. Open its Network entry and note the method, request URL, Content-Type, payload fields, and response. The Web2Generators article on checking website network requests explains where to find this information in Chrome, Firefox, Edge, and Safari.

Then construct the smallest cURL request that represents the same application-level input. The cURL command guide provides more examples of headers, authentication, quoting, and troubleshooting options.

A useful debugging sequence is:

  1. Start with a known-working request in a test environment, keeping any required authentication with test credentials.
  2. Change one thing: format, header, or field value.
  3. Capture the status code and response body.
  4. Confirm what the server actually received, using authorized server logs if available.
  5. Remove unnecessary headers one at a time while preserving required authentication.

If you change five variables at once, the resulting error tells you very little.

HTML escaping is not form encoding

Sometimes the same characters appear in HTML source and in form data, but the escaping rules have different purposes. In HTML source, an ampersand in text may be written as &amp;. In a form body, a literal ampersand inside a value is encoded as %26; a raw & normally separates fields. Neither operation is a substitute for the other.

The HTML encoder and decoder and the article HTML Escaping vs Sanitizing: When to Use Each explain the HTML side. Apply the escaping rule for the actual context where data is being used.

A reliable request checklist

Before sharing an API reproduction, make sure the method and endpoint are correct, the body matches the documented schema, Content-Type describes the bytes actually sent, and Accept is set only where needed. Preserve repeated form fields when the server supports them. For multipart uploads, let cURL create the boundary. Strip credentials from any report, and never upload private production files to a test service.

Build your first JSON POST

Open the free cURL command line builder, choose POST, enter your test endpoint, and select application/json under Content-Type. Paste {"name":"Example notebook","quantity":2} into Request body, then select Copy command and run it in Bash or Zsh. The builder prepares the command locally; it does not send the request or validate the JSON. Compare the response with your API’s documented fields before adding more options.

Premium is coming soon

An optional premium subscription to remove ads is planned. Subscriptions are not available yet.

You can keep using our free tools without an account.