Back to HTTP Headers

Accept content request

Tells the server which content types the client can handle — the client's half of HTTP content negotiation.

What it does

Accept tells the server which media types the client can process, in order of preference. The server uses this to pick the best representation to return and declares its choice in Content-Type. This is HTTP content negotiation — client and server agree on format without the URL changing.

Syntax

Accept: <media-type>
Accept: <media-type>, <media-type>;q=<quality>
Accept: */*
Accept: text/*

Examples:

Accept: text/html
Accept: application/json
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
Accept: application/json, application/xml;q=0.9, */*;q=0.5

Quality values (q-factors)

When a client accepts multiple types, q values express preference — from 1.0 (most preferred) to 0.0 (not acceptable). Default is 1.0:

Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

Reading this:

  • text/html → q=1.0 (most preferred)
  • application/xhtml+xml → q=1.0
  • application/xml → q=0.9
  • */* → q=0.8 (anything else, least preferred)

The server picks the highest-q type it can produce. If it can serve text/html, it does. If not, it tries application/xhtml+xml, and so on.

Wildcards

Accept: */*          ← Accept anything
Accept: text/*       ← Any text type (text/html, text/plain, text/csv...)
Accept: image/*      ← Any image type

Wildcards let clients express broad compatibility without listing every type.

What browsers actually send

A typical browser Accept header:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

The browser strongly prefers HTML, accepts XML with lower priority, accepts modern image formats, and falls back to anything. This is why navigating to a URL that serves application/json in a browser usually results in a raw JSON display rather than the server auto-detecting "this is a browser" — it's literally telling the server it'll accept anything.

Accept in API clients

API clients typically send a tighter Accept:

Accept: application/json

Or with fallback:

Accept: application/json, application/xml;q=0.9

This is how a single API endpoint can serve JSON to API clients and HTML to browsers — the same URL, different Accept headers, different Content-Type responses. The Vary: Accept response header tells caches to store separate versions per Accept value.

406 Not Acceptable

When the server can't produce any type from the client's Accept list, it returns 406 Not Acceptable:

GET /api/data HTTP/1.1
Accept: application/xml

HTTP/1.1 406 Not Acceptable
Content-Type: application/json

{"error": "Only application/json is supported"}

In practice, most servers ignore Accept mismatches and return their default format anyway rather than 406 — strict content negotiation is more common in theory than practice.

How it interacts with other headers

Accept pairs with Content-Type (response): client declares what it'll accept; server declares what it's returning.

Vary: Accept on the response tells caches to store separate versions per Accept value — critical if the same URL genuinely serves different formats.

Accept-Encoding, Accept-Language, and Accept-Charset are siblings — same pattern, different negotiation dimension.

Common mistakes and gotchas

Server ignoring Accept entirely. Many frameworks return their default format regardless of Accept. This breaks API clients expecting JSON when a text/html request somehow hits the endpoint. Implement proper content negotiation or at minimum validate Accept and return 406 on mismatch.

Not setting Vary: Accept when doing content negotiation. If your endpoint returns JSON or HTML based on Accept, caches will store one and serve it to everyone. Vary: Accept tells caches to key by the Accept value.

Sending Accept: application/json for form submissions. HTML forms send Accept: text/html by default. If you manually set Accept: application/json on a form submission, the server expects to return JSON — the browser then displays raw JSON instead of rendering a redirect or HTML response.

Real-world examples

Browser requesting a page:

GET /about HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

API client requesting JSON:

GET /api/users/1 HTTP/1.1
Accept: application/json
Authorization: Bearer mytoken

Client accepting multiple formats with preference:

GET /data HTTP/1.1
Accept: application/json;q=1.0, application/xml;q=0.8, text/csv;q=0.5

FAQ

What if I don't send an Accept header?

The server treats it as Accept: */* — willing to accept anything. The server returns whatever its default format is.

Can Accept be used to request image formats?

Yes. Browsers use Accept: image/avif,image/webp,image/apng,*/*;q=0.8 on image requests, which is how servers can serve WebP or AVIF to browsers that support them and JPEG/PNG as fallback to those that don't — from the same <img src="..."> URL.

Does Accept affect caching?

Only if the server sets Vary: Accept. Without it, a cache that receives a JSON response might serve it to a browser expecting HTML. With Vary: Accept, caches store separate entries per Accept value.

Fun fact

HTTP content negotiation via Accept was one of the web's original "killer features" — Tim Berners-Lee envisioned a web where the same URL transparently served different formats (HTML for browsers, plain text for terminals, different languages for different users) based on negotiated headers. In practice, the web largely went a different direction: different URLs for different formats, language negotiation via subpaths or query params, and format negotiation via file extensions. Accept found its niche mainly in REST APIs (JSON vs XML negotiation) and image format negotiation (WebP/AVIF) — useful, but a fraction of what was originally envisioned.