Beginner20 min read

HTTP Headers & Content Types

Learn HTTP headers, content negotiation, authentication headers, CORS, caching, and best practices for request and response headers in REST APIs.

What Are HTTP Headers?

HTTP headers are metadata sent along with requests and responses. They provide additional information about the message — what format the data is in, who is making the request, how to cache the response, and more.

Every HTTP message has two parts:

  1. Headers — Key-value pairs of metadata (invisible to users)
  2. Body — The actual content (JSON data, HTML, files, etc.)

Headers are structured as Name: Value pairs. Here is an example request:

html
GET /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json
User-Agent: Mozilla/5.0

And the corresponding response:

html
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
Access-Control-Allow-Origin: *

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

Headers are invisible in the browser, but they control everything about how HTTP works. Understanding headers is essential for building robust APIs.

Content-Type and Accept Headers

Two of the most important headers control what format the data is in and what format the client wants.

Content-Type — What format is this message? The Content-Type header tells the recipient what format the body is in. It is sent by both clients (in POST/PUT requests) and servers (in responses).

Common Content-Type values:

  • application/json — JSON data (most common for REST APIs)
  • application/x-www-form-urlencoded — HTML form data (key1=value1&key2=value2)
  • multipart/form-data — File uploads with forms
  • text/html — HTML page
  • text/plain — Plain text
  • application/xml — XML data
  • image/png, image/jpeg — Image files

Example:

html
POST /users
Content-Type: application/json

{"name": "Alice", "email": "alice@example.com"}

Accept — What format do I want back? The Accept header is sent by the client to tell the server what format it prefers for the response. This is called content negotiation.

Example:

html
GET /users/123
Accept: application/json

The server should respond with JSON:

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

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

If the server cannot provide the requested format, it returns 406 Not Acceptable. In practice, most modern APIs only support JSON and ignore the Accept header.

Authorization Header

The Authorization header is how clients prove their identity. It is used for authentication — proving you are who you say you are.

The most common pattern is Bearer tokens (typically JWTs):

html
GET /users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyM30.signature

The format is: Authorization: Bearer <token>

When the server receives this request, it:

  1. Extracts the token from the Authorization header
  2. Verifies the token signature
  3. Decodes the token to get the user ID
  4. Proceeds with the request as that user

Other authorization schemes exist:

  • Authorization: Basic base64(username:password) — Basic auth (rarely used for APIs)
  • Authorization: Digest ... — Digest auth (obsolete)
  • Authorization: OAuth token — OAuth 2.0

But Bearer tokens are by far the most common in modern REST APIs.

Important: Never send sensitive tokens in URL query parameters (/users?token=abc123). URLs are logged everywhere — server logs, proxy logs, browser history. Always use the Authorization header for authentication.

CORS Headers

CORS (Cross-Origin Resource Sharing) headers control which websites can call your API from the browser. This is essential for frontend-backend communication.

The Problem: Same-Origin Policy By default, browsers block JavaScript on https://myapp.com from making requests to https://api.example.com. This is a security feature called the same-origin policy. Without it, malicious websites could steal data from other sites.

But what if you WANT myapp.com to call api.example.com? You use CORS headers.

The Solution: CORS Headers The server sends these headers to tell the browser "it is okay, allow this request":

Access-Control-Allow-Origin — Which origins are allowed?

Access-Control-Allow-Origin: https://myapp.com

Or allow all origins (use cautiously):

Access-Control-Allow-Origin: *

Access-Control-Allow-Methods — Which HTTP methods are allowed?

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers — Which headers can the client send?

Access-Control-Allow-Headers: Content-Type, Authorization

Access-Control-Allow-Credentials — Can the client send cookies?

Access-Control-Allow-Credentials: true

Preflight Requests For non-simple requests (POST with JSON, requests with custom headers), the browser sends an OPTIONS request first to check if CORS is allowed. This is called a preflight. Your API must respond to OPTIONS requests with the appropriate CORS headers.

Example preflight:

html
OPTIONS /users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

Response:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization

Without proper CORS headers, frontend apps cannot call your API.

Caching and Rate Limiting Headers

Headers also control caching (storing responses to avoid redundant requests) and rate limiting (preventing abuse).

Cache-Control — How long can this response be cached?

Cache-Control: max-age=3600

This means "you can reuse this response for up to 3600 seconds (1 hour) without asking the server again".

Other values:

  • Cache-Control: no-cache — You can cache it, but check with the server before reusing (conditional request)
  • Cache-Control: no-store — Do not cache this at all (sensitive data)
  • Cache-Control: public — Any cache (browser, CDN) can store this
  • Cache-Control: private — Only the browser can cache this, not shared caches

Caching improves performance dramatically. If your API returns the same data frequently (like a user profile or a list of countries), cache it.

ETag — Conditional Requests The ETag header is a fingerprint of the response content. If the client makes the same request later, it sends the ETag back in an If-None-Match header. If the content hasn't changed, the server returns 304 Not Modified with no body, saving bandwidth.

html
GET /users/123
Response:
200 OK
ETag: "abc123xyz"
{"id": 123, "name": "Alice"}

Later:
GET /users/123
If-None-Match: "abc123xyz"

Response (if unchanged):
304 Not Modified

Rate Limiting Headers When your API enforces rate limits, tell clients about it:

html
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1640995200

When the limit is exceeded:

html
429 Too Many Requests
Retry-After: 60

This helps clients implement exponential backoff and avoid getting blocked.

A client sends a POST request to create a user but forgets to include the Content-Type header. What should the API do?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.