Content-Type vs Accept Header

Complete guide — what each header does, when to use them, and common mistakes

HTTP & APIsJune 26, 202614 min readBy Keyur Patel

Content-Type and Accept are two of the most commonly confused HTTP headers. They look similar, often have the same value (application/json), and both deal with data formats — but they serve completely different purposes. Confusing them causes broken API requests, 400 errors, and hours of debugging. This guide clarifies exactly what each header does, when to use them, and how they work together in HTTP content negotiation.

The One-Sentence Difference

📤

Content-Type

"Here is the format of the data I am sending you"

📥

Accept

"Here is the format I want to receive back"

Real-World Analogy: The Restaurant

Imagine ordering at a restaurant:

You (client) tell the waiter: "I'd like my meal served on a plate" → This is the Accept header (what format you want to receive)

You (client) hand the waiter a written note with your order → The note's format is the Content-Type of your request (what you're sending)

Kitchen (server) prepares the food and serves it on a plate → The plate is the Content-Type of the response (what the server is sending back)

Content-Type vs Accept: Complete Comparison

FeatureContent-TypeAccept
PurposeDescribes format of the BODY being sentDescribes format the client wants to RECEIVE
Who sends itBoth client (request) and server (response)Only the client (in request)
DirectionDescribes current message bodyDescribes desired response body
Required when?When request/response has a bodyOptional (but recommended for APIs)
In GET requests?No (GET has no body)Yes (tells server what format you want)
In POST requests?Yes (describes the body format)Yes (tells server what format to return)
In responses?Yes (always describes response body)No (Accept is only a request header)
Default if missingServer may reject or guessServer uses its default format
Common valuesapplication/json, multipart/form-dataapplication/json, text/html, */*
Browser sets it?Automatically for formsAutomatically for page loads

What Is the Content-Type Header?

The Content-Type header describes the format (MIME type) of the data in the HTTP message body. It answers the question: "What format is this data in?" — so the recipient knows how to parse it.

Content-Type is unique because it appears in both requests and responses:

  • In requests: Tells the server what format the request body is in. "I'm sending you JSON, so use your JSON parser."
  • In responses: Tells the browser/client what format the response body is in. "This response is HTML, so render it as a page."

Without Content-Type, the server has no reliable way to know whether you sent JSON, XML, form data, or a binary file. It would have to guess — and guessing leads to parsing failures. This is why most 400 Bad Request errors in APIs are caused by a missing or incorrect Content-Type header.

Content-Type in Requests

When you send a POST, PUT, or PATCH request with a body, Content-Type tells the server how to read that body. The server's middleware (like Express's express.json()) looks at Content-Type to decide which parser to activate:

// JSON body → server activates JSON parser
Content-Type: application/json
Body: {"name": "Alice", "age": 30}

// Form data → server activates URL-encoded parser
Content-Type: application/x-www-form-urlencoded
Body: name=Alice&age=30

// File upload → server activates multipart parser
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Body: [binary file data split by boundary]

Content-Type in Responses

In server responses, Content-Type tells the browser how to handle what it received. This determines whether the browser renders HTML, parses JSON, displays an image, or triggers a file download:

Content-Type: text/html; charset=utf-8      → Browser renders as web page
Content-Type: application/json               → Browser/JS parses as JSON object
Content-Type: image/png                      → Browser displays as image
Content-Type: application/pdf                → Browser opens PDF viewer or downloads
Content-Type: text/css                       → Browser applies as stylesheet
Content-Type: application/javascript         → Browser executes as script

If Content-Type is wrong in a response, things break visibly. A JSON API that returns Content-Type: text/html will cause fetch().json() to fail. An HTML page served as application/octet-stream will trigger a download instead of rendering.

What Is the Accept Header?

The Accept header is a request-only header that tells the server what response format the client prefers. It answers: "When you send me the response, please use this format."

Unlike Content-Type (which describes data already being sent), Accept describes data the client wants to receive in the future — it's a preference, not a description of current content.

Accept is critical for APIs that support multiple response formats. A single endpoint like /api/users might return JSON (for JavaScript clients), XML (for legacy systems), or CSV (for spreadsheet exports) — the Accept header determines which format the server chooses.

Accept with Quality Values

The Accept header supports quality values (q=0 to q=1) that indicate preference order. Higher q values mean higher preference. No q value means q=1 (highest):

// "I prefer JSON, but I'll accept HTML or plain text"
Accept: application/json, text/html;q=0.9, text/plain;q=0.8

// Breakdown:
// application/json     → q=1.0 (default, highest priority)
// text/html            → q=0.9 (second choice)
// text/plain           → q=0.8 (third choice)

// "I accept anything"
Accept: */*

// "I want images, preferring PNG over JPEG"
Accept: image/png, image/jpeg;q=0.8, image/*;q=0.5

What Browsers Send Automatically

Browsers set Accept headers automatically based on the type of request:

Page navigation (clicking a link)text/html, application/xhtml+xml, */*;q=0.8
Loading a stylesheet (<link>)text/css, */*;q=0.1
Loading a script (<script>)*/*
Loading an image (<img>)image/avif, image/webp, image/png, */*;q=0.8
fetch() with no Accept set*/* (accept anything)
XMLHttpRequest*/*

Notice that browsers prefer modern image formats (avif, webp) over older ones. This is content negotiation in action — the server can return WebP to modern browsers while serving PNG to older ones, all from the same URL.

Content-Type in File Uploads

File uploads are where Content-Type gets tricky. The most common mistake developers make is manually setting Content-Type for FormData — which actually breaks the upload.

How multipart/form-data Works

When you upload a file, the request body is split into multiple "parts" separated by a unique boundary string. Each part has its own Content-Type (the file's type) and Content-Disposition (the field name). The overall request Content-Type must include this boundary for the server to know where each part begins and ends.

// What the browser actually sends for a file upload:
POST /api/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

Alice
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[binary JPEG data here]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

The boundary is randomly generated by the browser. If you manually set Content-Type: multipart/form-data without the boundary, the server cannot parse the parts and the upload fails silently. This is why you should never set Content-Type manually when using FormData — let the browser handle it.

⚠️ Pro Tip

When using fetch() with FormData as the body, do NOT set Content-Type in headers. The browser will set it automatically with the correct boundary. If you set it manually, you break the upload.

Common MIME Types

Both headers use MIME types as values. Here are the ones you'll encounter daily:

MIME TypeUsed ForContext
application/jsonJSON dataREST APIs, fetch, Axios
text/htmlHTML pagesBrowser page loads, server-rendered apps
text/plainPlain textSimple text responses, logs
application/xmlXML dataSOAP APIs, legacy systems
multipart/form-dataFile uploads + form fieldsFile upload forms
application/x-www-form-urlencodedHTML form dataDefault form submission encoding
image/pngPNG imagesImage responses, uploads
image/jpegJPEG imagesPhoto uploads, thumbnails
application/pdfPDF documentsDocument downloads
application/octet-streamBinary data (generic)Any file download
text/cssCSS stylesheetsBrowser style requests
application/javascriptJavaScript filesBrowser script requests

How Content Negotiation Works

Content negotiation is the process where client and server agree on a response format:

1. Client sends Accept header

Accept: application/json, text/html;q=0.9

2. Server checks what it supports

Can produce: JSON ✅ HTML ✅ XML ❌

3. Server responds with best match

Content-Type: application/json (highest quality match)

The q value (0-1) in Accept indicates preference. application/json without q defaults to q=1 (highest priority). text/html;q=0.9 means "I prefer JSON but HTML is acceptable too."

Practical Code Examples

Fetch API — Sending JSON

// POST JSON to an API
const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",   // ← I'm SENDING JSON
    "Accept": "application/json"          // ← I want to RECEIVE JSON
  },
  body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
})

// GET request — only Accept needed (no body = no Content-Type)
const user = await fetch("https://api.example.com/users/1", {
  headers: {
    "Accept": "application/json"  // ← Only Accept (GET has no body)
    // No Content-Type needed — GET requests have no body!
  }
})

Fetch API — File Upload

// File upload with FormData
const formData = new FormData()
formData.append("avatar", fileInput.files[0])
formData.append("name", "Alice")

const response = await fetch("/api/upload", {
  method: "POST",
  // ⚠️ Do NOT set Content-Type manually for FormData!
  // The browser sets it automatically with the correct boundary
  headers: {
    "Accept": "application/json"  // ← I want JSON response
  },
  body: formData
})

// ❌ Common mistake:
// headers: { "Content-Type": "multipart/form-data" }  
// This BREAKS file uploads — browser needs to set the boundary itself

Axios

// Axios sets Content-Type: application/json automatically for objects
const { data } = await axios.post("/api/users", {
  name: "Alice",
  email: "alice@example.com"
})
// Axios adds: Content-Type: application/json (automatic)
// Axios adds: Accept: application/json, text/plain, */* (default)

// Explicit headers
const { data } = await axios.get("/api/users/1", {
  headers: {
    "Accept": "application/json"
  }
})

cURL

# POST JSON with both headers
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name": "Alice"}'

# GET with Accept only
curl -H "Accept: application/json" https://api.example.com/users/1

# File upload — curl sets Content-Type automatically with -F
curl -X POST https://api.example.com/upload \
  -H "Accept: application/json" \
  -F "avatar=@photo.jpg" \
  -F "name=Alice"

Common Developer Mistakes

⚠️ Setting Content-Type on GET requests

GET requests have no body, so Content-Type is meaningless. Some servers might even reject the request. Only set Content-Type when you have a request body (POST, PUT, PATCH).

Fix: Remove Content-Type from GET and DELETE requests.

⚠️ Manually setting Content-Type: multipart/form-data

When using FormData, the browser automatically sets Content-Type with the correct boundary parameter. Setting it manually omits the boundary, breaking the upload.

Fix: Let the browser/library set Content-Type for FormData automatically.

⚠️ Confusing Accept with Content-Type

Sending 'Accept: application/json' when you mean 'Content-Type: application/json' (or vice versa) causes the server to misinterpret your request.

Fix: Remember: Content-Type = what I'm sending. Accept = what I want back.

⚠️ Sending JSON body without Content-Type: application/json

The server's JSON body parser won't activate without the correct Content-Type. Your request body arrives as undefined/null on the server, causing confusing bugs.

Fix: Always set Content-Type: application/json when sending JSON in the body.

⚠️ Using Accept: */* everywhere

While technically valid, */* gives you no control over response format. The server might return XML, HTML, or any format it prefers. Be specific about what you want.

Fix: Explicitly request the format you expect: Accept: application/json.

⚠️ Not setting Accept on API requests

Some APIs support multiple formats (JSON + XML). Without an explicit Accept header, you might get XML when you expected JSON, breaking your parser.

Fix: Always include Accept: application/json for API requests.

Quick Reference: When to Use Each Header

ScenarioContent-TypeAccept
GET /api/users❌ Not needed✅ application/json
POST /api/users (JSON body)✅ application/json✅ application/json
POST /upload (file)❌ Browser sets it✅ application/json
PUT /api/users/1 (update)✅ application/json✅ application/json
DELETE /api/users/1❌ Not neededOptional
HTML form submitAuto: x-www-form-urlencodedAuto: text/html
GraphQL query✅ application/json✅ application/json
Download PDF❌ Not needed (GET)✅ application/pdf

Best Practices

Always set Content-Type when sending a body

POST, PUT, and PATCH requests need Content-Type so the server knows how to parse the body.

Always set Accept on API requests

Don't rely on server defaults. Explicitly request application/json to guarantee the response format.

Never set Content-Type on GET/DELETE

These methods have no body — Content-Type is meaningless and can confuse some servers.

Don't manually set Content-Type for FormData

The browser needs to set the multipart boundary. Manual override breaks file uploads.

Include charset for text types

Use 'Content-Type: text/html; charset=utf-8' to prevent encoding issues.

Use quality values in Accept for fallbacks

'Accept: application/json, text/plain;q=0.9' — prefers JSON but accepts plain text as fallback.

Match Content-Type to your actual body format

Sending form data? Use x-www-form-urlencoded. Sending JSON? Use application/json. Mismatches break parsing.

Let frameworks handle defaults when possible

Axios, Express, and most frameworks set Content-Type automatically for common cases. Don't override unless needed.

Interview Questions

Q: What is the difference between Content-Type and Accept?

A: Content-Type describes the format of the data being sent (request body or response body). Accept describes the format the client wants to receive back. Content-Type = 'what I'm giving you'. Accept = 'what I want back'.

Q: Can Content-Type and Accept have the same value?

A: Yes. When you POST JSON and want JSON back, both are application/json. This is very common in REST APIs. But they serve different purposes — one describes what you send, the other describes what you want.

Q: Which header does the browser send automatically?

A: Browsers set Accept automatically for page loads (text/html), image requests (image/*), and style requests (text/css). For forms, browsers set Content-Type automatically (application/x-www-form-urlencoded or multipart/form-data).

Q: What happens if Content-Type is wrong?

A: The server's body parser won't match the actual data format. Sending JSON with Content-Type: text/plain means the JSON parser never runs — the body arrives as undefined/null. Typically results in 400 Bad Request.

Q: What happens if Accept is missing?

A: The server uses its default response format. For most APIs, that's JSON. But some multi-format APIs might return XML or HTML. It's best practice to always specify Accept explicitly.

Q: Explain content negotiation.

A: Content negotiation is the HTTP mechanism where the client tells the server what formats it accepts (via Accept header with quality values), and the server picks the best matching format it supports and returns it with the appropriate Content-Type response header.

Frequently Asked Questions

What is the Content-Type header?
Content-Type tells the recipient what format the body data is in. In requests, it tells the server 'I am sending JSON' (Content-Type: application/json). In responses, it tells the browser 'this response is HTML' (Content-Type: text/html).
What is the Accept header?
The Accept header tells the server what format the client wants the RESPONSE in. 'Accept: application/json' means 'please send me JSON'. It is only used in requests and only affects the response format.
Can Content-Type and Accept have the same value?
Yes. When you send JSON and want JSON back, both are application/json. But they serve different purposes — Content-Type describes what you're sending, Accept describes what you want to receive.
Do GET requests need a Content-Type header?
No. GET requests have no body, so Content-Type is unnecessary and should not be set. Only set Content-Type when you have a request body (POST, PUT, PATCH).
What happens if Content-Type is wrong?
The server will fail to parse the request body. For example, sending JSON with Content-Type: text/plain causes the server's JSON parser to never activate, resulting in an empty or unparsed body and typically a 400 Bad Request error.
What happens if Accept header is missing?
Most servers default to their primary format (usually JSON for APIs, HTML for web pages). The request won't fail, but you lose control over what format you receive. Some APIs might return XML instead of JSON if Accept is missing.
What is application/json?
application/json is the MIME type for JSON data. It tells the server or browser that the data is formatted as JSON and should be parsed accordingly. It is the most common Content-Type for modern REST APIs.
What is multipart/form-data?
multipart/form-data is used for file uploads. It splits the request body into multiple parts (each with its own content type), allowing you to send files alongside text fields in a single request.
What are MIME types?
MIME types (Multipurpose Internet Mail Extensions) are standardized labels that identify the format of data. Examples: application/json, text/html, image/png. They are used in Content-Type and Accept headers to specify data formats.
How does content negotiation work?
Content negotiation is the process where the client tells the server what formats it accepts (via Accept header), and the server responds in the best matching format (setting Content-Type in the response). This allows one endpoint to serve JSON, XML, or HTML based on what the client requests.
How does the browser decide Content-Type?
For form submissions, the browser sets Content-Type based on the form's enctype attribute (default: application/x-www-form-urlencoded, or multipart/form-data for file uploads). For fetch/XHR, you must set it manually.
What is the difference between Content-Type and Accept?
Content-Type describes the format of data being SENT (request body or response body). Accept describes the format of data the client wants to RECEIVE. Content-Type = 'what I'm giving you'. Accept = 'what I want back'.

Related Articles & Tools

Conclusion

Content-Type and Accept are two sides of the same coin in HTTP communication. Content-Type describes the format of the data being sent — use it on POST, PUT, and PATCH requests and in every server response. Accept describes the format the client wants to receive — use it on every API request to control the response format.

Together, they enable content negotiation — the mechanism that lets one API serve JSON to mobile apps, HTML to browsers, and XML to legacy systems from the same endpoint. Master these two headers and you eliminate an entire category of API debugging headaches.

Remember the simple rule: Content-Type = "what I'm sending you." Accept = "what I want back."