Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/http-gateways/path-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ These are the equivalents:
- `format=ipns-record` → `Accept: application/vnd.ipfs.ipns-record`

When both `Accept` HTTP header and `format` query parameter are present,
`Accept` SHOULD take precedence.
`format` SHOULD take precedence.

:::note

Expand Down
14 changes: 6 additions & 8 deletions src/http-gateways/trustless-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,15 @@ Same as [`format`](https://specs.ipfs.tech/http-gateways/path-gateway/#format-re
- `format=car` → `application/vnd.ipld.car`
- `format=ipns-record` → `application/vnd.ipfs.ipns-record`

When both `Accept` HTTP header and `format` query parameter are present,
`format` SHOULD take precedence.

:::note

A Client SHOULD include the `format` query parameter in the request URL, in
addition to the `Accept` header. This provides the best interoperability and
ensures consistent HTTP cache behavior across various gateway implementations.

When both the `Accept` header and `format` parameter are present, a specific
`Accept` value (e.g., `application/vnd.ipld.raw`) SHOULD take precedence over
`format`. Wildcards (e.g., `*/*`, `application/*`) are not specific and do not
take precedence (as specified in :cite[path-gateway]).

:::

### :dfn[`dag-scope`] (request query parameter)
Expand Down Expand Up @@ -239,23 +237,23 @@ Optional, only used on CAR requests.

Serves same purpose as [CAR `version` content type parameter](#car-version-content-type-parameter).

In case both are present in the request, the value from the [`Accept`](#accept-request-header) HTTP Header has priority and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.
In case both are present in the request, the URL query parameter SHOULD take precedence and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.

### :dfn[`car-order`] (request query parameter)

Optional, only used on CAR requests.

Serves same purpose as [CAR `order` content type parameter](#car-order-content-type-parameter).

In case both are present in the request, the value from the [`Accept`](#accept-request-header) HTTP Header has priority and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.
In case both are present in the request, the URL query parameter SHOULD take precedence and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.

### :dfn[`car-dups`] (request query parameter)

Optional, only used on CAR requests.

Serves same purpose as [CAR `dups` content type parameter](#car-dups-content-type-parameter).

In case both are present in the request, the value from the [`Accept`](#accept-request-header) HTTP Header has priority and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.
In case both are present in the request, the URL query parameter SHOULD take precedence and a matching [`Content-Location`](#content-location-response-header) SHOULD be returned with the response.

# HTTP Response

Expand Down
166 changes: 166 additions & 0 deletions src/ipips/ipip-0523.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: "IPIP-0523: Prefer format param over Accept header"
date: 2025-12-11
ipip: proposal
editors:
- name: Alex Potsides
github: achingbrain
url: https://achingbrain.net
affiliation:
name: Shipyard
url: https://ipshipyard.com
- name: Marcin Rataj
github: lidel
affiliation:
name: Shipyard
url: https://ipshipyard.com
relatedIssues:
- https://github.com/ipfs/specs/issues/521
thanks:
- name: Adin Schmahmann
github: aschmahmann
affiliation:
name: Shipyard
url: https://ipshipyard.com
order: 523
tags: ['ipips']
---

## Summary

Prefer the `?format=` URL query parameter over the `Accept:` HTTP request header.

## Motivation

The [`Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept)
HTTP request header can be sent with an HTTP request to provide a prioritized
list of response formats the client will accept for a given resource.

The [`format`](https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter)
URL query parameter can also be sent to an IPFS HTTP Gateway to provide the
same information, and is typically done when sending an HTTP header is difficult
or impractical, for example when using a browser address bar.

The existing [Path Gateway](https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter) and [Trustless Gateway](https://specs.ipfs.tech/http-gateways/trustless-gateway/#format-request-query-parameter) specs say:

> When both the Accept header and format parameter are present, a specific Accept value (e.g., application/vnd.ipld.raw) SHOULD take precedence over format.

This makes it impossible for browsers to use the `format` URL query parameter,
since they will always send an `Accept` HTTP header which would then cause
the `format` URL query parameter to be ignored.

## Detailed design

The priority of the `format` URL query parameter vs the `Accept` HTTP header is
reversed in both :cite[path-gateway] and :cite[trustless-gateway] specs: the
`format` URL query parameter SHOULD always take precedence over the `Accept`
HTTP header when both are present.

This simplifies the specification by removing the previous wildcard exception
logic. Implementations no longer need to distinguish between specific `Accept`
header values (e.g., `application/vnd.ipld.raw`) and wildcards (e.g., `*/*`)
when determining precedence.

## Design rationale

Browsers will always send an `Accept` HTTP header that contains specific values
so it cannot be allowed to take priority over the `format` URL query parameter.

### User benefit

Users will be able to use the `format` URL query parameter to control the
response type of requests made from browser address bars.

### Compatibility
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ @achingbrain fyi I've expanded the Compatibility section. While formally this is a breaking change, in practice boxo/gateway already had an escape hatch in customResponseFormat which prioritized format if Accept has a wildcard or used something other than raw, car or ipns-record

(imo this is good news, means IPIP is actually cleaning up and formalizing real world behavior in browsers)


This change simplifies precedence rules by making the `format` URL query
parameter always take priority over the `Accept` HTTP header when both are
present.

In practice, this is largely compatible with existing web browser use cases.
Browsers send `Accept` HTTP headers with wildcards (e.g., `*/*` or
`text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`), and
the previous spec already treated wildcards as non-specific, allowing `format`
to take precedence. This means browser address bar usage with `?format=` was
already working as expected.

In recent years we also realized that HTTP cache implementations are often
flawed, and virtually all HTTP clients add explicit `?format=` anyway to ensure
a unique HTTP cache key is used for each URL. This provides extra protection
from poorly written or configured software and CDNs that comingle different
response types under the same cache key (e.g., deserialized response, raw block,
and CAR being cached and returned based on what was requested and cached first).
By prioritizing `?format=` we ensure deterministic HTTP caching behavior across
the ecosystem, making it easier to deploy and reason about HTTP trustless
gateways.

The actual breakage is limited to edge cases where a client sends both a
specific `Accept` HTTP header (e.g., `Accept: application/vnd.ipld.raw`) and a
different `format` URL query parameter (e.g., `?format=car`). Previously, the
specific `Accept` header value would win; now `format` wins. This scenario is
rare in practice and arguably represents a client misconfiguration.

The primary impact is on [gateway-conformance](https://github.com/ipfs/gateway-conformance/)
tests, which explicitly test the old precedence behavior. A minor version bump
of gateway-conformance is required to update these tests.

### Security

This change has no security implications. It only affects which response format
is returned when a client sends conflicting format preferences, and does not
change authentication, authorization, or data integrity behaviors.

### Alternatives

#### Keep status quo with wildcard exception

The previous spec already had a
carve-out where wildcards (e.g., `*/*`, `application/*`) in the `Accept` HTTP
header did not take precedence over the `format` URL query parameter. This meant
browser use cases were effectively supported, since browsers include wildcards
in their default `Accept` HTTP headers.

This alternative was rejected because:

1. The wildcard exception adds implementation complexity
2. The resulting behavior is harder to reason about and document
3. The simpler rule ("format always wins") is easier to understand and implement
4. Real-world browser use cases work identically under both rules

#### Return HTTP 400 on conflicting format preferences

Require gateways to return HTTP 400 when the `Accept` header and `format`
parameter specify different formats, signaling client misconfiguration.

This was rejected as impractical:

1. **Reintroduces complexity**: Detecting "conflicts" requires fully parsing
`Accept` headers, handling wildcards and quality values - the very complexity
this IPIP eliminates.

2. **Incompatible with real-world HTTP infrastructure**: CDNs and reverse proxies
(Nginx, Cloudflare, etc.) routinely strip, transform, or ignore `Accept`
headers. When requests traverse multiple hops, each with different behaviors,
the `Accept` header often doesn't reach the gateway intact. The URL is the
only reliable cache key across the entire stack.

3. **Untestable in practice**: Conformance tests expecting HTTP 400 would
spuriously pass or fail depending on whether upstream infrastructure forwarded
the `Accept` header. This creates a specification that cannot be reliably
verified.

The chosen approach - `format` always takes precedence, no error on disagreement -
is a pragmatic choice that minimizes implementation complexity and maximizes
interoperability with real-world HTTP clients, CDNs, reverse proxies, and caches.

## Test fixtures

Implementers can either write own test that prefers the `format` URL query
parameter over any present `Accept` HTTP header, or run the
[gateway-conformance](https://github.com/ipfs/gateway-conformance/) test suite,
which includes tests for this scenario since
[gateway-conformance/pull/252](https://github.com/ipfs/gateway-conformance/pull/252).

### Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).