Skip to main content

Content Negotiation in REST APIs: A Practical Guide

·APIScout Team
content negotiationrest apimedia typesapi designbest practices

Content Negotiation in REST APIs: A Practical Guide

Content negotiation lets clients and servers agree on the response format — JSON, XML, CSV, or custom media types. The client says what it wants via the Accept header, the server returns the best match. In practice, most APIs only support JSON. But understanding content negotiation unlocks API versioning, format flexibility, and proper HTTP semantics.

How It Works

Request

GET /api/users/123
Accept: application/json

Response

HTTP/1.1 200 OK
Content-Type: application/json

{"id": 123, "name": "John"}

If the server can't produce the requested format, it returns 406 Not Acceptable.

The Accept Header

Clients specify preferred formats with quality values (0-1):

Accept: application/json, application/xml;q=0.9, text/csv;q=0.5

This means: prefer JSON, XML is acceptable, CSV is last resort. Quality defaults to 1.0 if not specified.

Common Media Types

Media TypeUse Case
application/jsonDefault for APIs
application/xmlLegacy enterprise APIs
text/csvData export, spreadsheets
application/pdfDocument generation
text/htmlBrowser-readable responses
application/octet-streamBinary file download
multipart/form-dataFile uploads
text/event-streamServer-Sent Events

Custom Media Types

Custom media types encode API-specific information:

Accept: application/vnd.yourapi.user.v2+json

Format: application/vnd.{vendor}.{resource}.{version}+{format}

GitHub's Approach

Accept: application/vnd.github.v3+json
Accept: application/vnd.github.v3.raw    # Raw file content
Accept: application/vnd.github.v3.html   # HTML rendered content
Accept: application/vnd.github.v3.diff   # Diff format
Accept: application/vnd.github.v3.patch  # Patch format

GitHub uses custom media types to control both the API version and the response format for the same resource.

Content Negotiation for Versioning

Instead of URL path versioning (/v1/users), version via the Accept header:

# Version 1
Accept: application/vnd.yourapi.v1+json

# Version 2
Accept: application/vnd.yourapi.v2+json

Pros: Clean URLs, per-resource versioning, RESTful. Cons: Harder to test (can't paste in browser), less visible, more complex routing.

Implementation Patterns

1. Default Format

Always have a default. If no Accept header is provided, return JSON:

GET /api/users → application/json (default)

2. Format via Extension (Pragmatic)

Some APIs support format via URL extension as a fallback:

GET /api/users.json
GET /api/users.csv
GET /api/users.xml

This isn't proper content negotiation, but it's practical and easy to test.

3. Format via Query Parameter

GET /api/users?format=csv

Also not proper content negotiation, but commonly used alongside Accept header support.

4. Response Format Matching

Your server should:

  1. Parse the Accept header
  2. Sort by quality value
  3. Find the first format you support
  4. Return 406 if no match

Practical Recommendations

For Most APIs

  1. Support application/json as the only format
  2. Return Content-Type: application/json on all responses
  3. Ignore the Accept header (always return JSON)
  4. Use URL path versioning instead of content negotiation for versions

This is what 95% of APIs do, and it's fine.

For APIs Needing Multiple Formats

  1. Support application/json (default) and one or two alternatives (CSV, XML)
  2. Respect the Accept header
  3. Return 406 Not Acceptable for unsupported formats
  4. Include Vary: Accept header for proper caching

For Enterprise APIs

  1. Full content negotiation with custom media types
  2. Version via Accept header
  3. Support JSON, XML, and potentially CSV/PDF
  4. Document supported media types in OpenAPI spec

Common Mistakes

MistakeImpactFix
Ignoring Accept header but returning wrong typeClient parse errorsRespect Accept or always return JSON
No 406 responseClient gets unexpected formatReturn 406 for unsupported formats
Missing Content-Type headerClient can't parse responseAlways include Content-Type
No Vary: Accept headerCDN caches wrong formatAdd Vary: Accept when supporting multiple formats
Over-engineering formatsMaintenance burdenStart with JSON only, add formats when needed

Designing REST APIs? Explore API design patterns and best practices on APIScout — architecture guides, comparisons, and developer resources.

Comments