A single URL can have more than one representation. The resource /products/42 might exist as JSON for an API client, as HTML for a browser, in English or French, and gzip compressed or not. Content negotiation is the agreement between client and server about which of those representations gets returned, without changing the URL.
The client drives the request side. It sends headers that rank what it can handle: Accept lists media types (application/json, text/html), Accept-Language lists languages (en-US, fr), Accept-Encoding lists compression schemes (gzip, br), and Accept-Charset lists character sets. Each value can carry a quality weight from 0 to 1 written as q, so a client can say it prefers French but will accept English with Accept-Language: fr;q=1.0, en;q=0.5.
The server inspects those headers, decides which representation best fits, and answers with Content-Type, Content-Language, and Content-Encoding describing exactly what it sent. It also adds a Vary header listing the request headers it used to decide, which is what tells caches like Cloudflare or a CDN that the response changes based on those headers.