QA Interview Hub · Knowledge base · Quizzes
API
Headers, caching and CORS
Key headers
Headers are metadata of a request and a response, and many bugs live in them that aren't visible in the body.
| Header | Where | What for |
|---|
Content-Type | Request and response | The body format, for example application/json |
Accept | Request | Which format the client wants to receive |
Authorization | Request | A token or credentials |
Location | Response | The URL of the created resource or a redirect |
Cache-Control | Response | Caching rules |
ETag | Response | The resource version for the cache and concurrent changes |
Retry-After | Response | When a request can be repeated after 429 or 503 |
Set-Cookie | Response | Set a cookie with security attributes |
Typical checks: the response's Content-Type matches the real content; for Accept: application/xml, if the API doesn't support it, the server returns 406, not JSON silently; the session cookie's Set-Cookie has HttpOnly, Secure and SameSite; the response has no headers that reveal the server or framework version.
In an interview: I check not only the body but also headers: Content-Type, Location after creation, caching rules and cookie security attributes. Errors in headers often break clients or create security problems.
ETag, 304 and Cache-Control
ETag is an identifier of a resource's version. The server returns ETag: "v7". Next time the client sends If-None-Match: "v7": if the resource hasn't changed, the server answers 304 Not Modified without a body, and the client takes the data from the cache. If it changed, 200 with a new body and a new ETag.
GET /accounts/42
If-None-Match: "v7"
HTTP/1.1 304 Not Modified
ETag: "v7"
ETag also protects against lost updates: the client sends a PUT with If-Match: "v7", and if someone has already changed the resource, the server returns 412 Precondition Failed.
Cache-Control: no-cache vs no-store is a classic trap:
no-cache: it may be stored, but before use its freshness must be validated with the server (through ETag or a date).no-store: don't store at all, neither in the browser nor in intermediate caches.max-age=60: consider the response fresh for 60 seconds; private: only in the user's browser, not in shared caches.
For personal and financial data I expect no-store or at least private, otherwise one user's balance may end up in a CDN cache and be served to another.
Test cases: after the resource changes, the ETag changes; an old ETag in If-None-Match gives 200, not 304; responses with personal data aren't cached by shared caches.
In an interview: ETag is a resource's version: with If-None-Match the server returns 304 if nothing changed. no-cache means "validate before use" and no-store means "don't store at all", and for financial data I expect exactly no-store.
CORS and preflight
CORS is a browser mechanism that allows or forbids JavaScript on one origin (scheme + domain + port) to read responses from another. Important: it is a restriction of the browser specifically. Postman, curl and server-side clients don't apply CORS, so "it works in Postman but not in the browser" is a typical sign of a CORS problem.
For "non-simple" requests, for example with Content-Type: application/json, an Authorization header or the PUT and DELETE methods, the browser first sends a preflight, an OPTIONS request:
OPTIONS /api/transfers
Origin: https://app.bank.test
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
The server must respond with Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers. If something is missing, the browser won't send the main request, and a CORS error appears in the Console.
What I check:
- The allowed front-end origin gets the right headers, and a foreign one doesn't.
- There is no
Access-Control-Allow-Origin: * together with passing cookies or tokens: the browser blocks that anyway, and reflecting any Origin back in the response is a security hole. - The preflight returns 2xx and doesn't require authorisation.
In an interview: CORS is browser protection, so it isn't visible in Postman. For non-simple requests the browser first sends a preflight OPTIONS, and I check that the server allows only trusted origins and the right methods and headers.