--- title: Path Gateway Specification description: > The comprehensive low-level HTTP Gateway enables the integration of IPFS resources into the HTTP stack through /ipfs and /ipns namespaces, supporting both deserialized and verifiable response types. date: 2026-08-24 maturity: reliable editors: - name: Marcin Rataj github: lidel url: https://lidel.org/ affiliation: name: Shipyard url: https://ipshipyard.com former_editors: - name: Henrique Dias github: hacdias url: https://hacdias.com/ affiliation: name: Shipyard url: https://ipshipyard.com thanks: - name: Adrian Lanzafame github: lanzafame affiliation: name: Protocol Labs url: https://protocol.ai/ - name: Vasco Santos github: vasco-santos affiliation: name: Protocol Labs url: https://protocol.ai/ - name: Oli Evans github: olizilla affiliation: name: Protocol Labs url: https://protocol.ai/ - name: Alex Potsides github: achingbrain url: https://achingbrain.net affiliation: name: Shipyard url: https://ipshipyard.com xref: - url - trustless-gateway - subdomain-gateway - dnslink-gateway - ipip-0402 - ipip-0412 - ipip-0288 - ipns-record tags: ['httpGateways', 'lowLevelHttpGateways'] order: 0 --- The most versatile form of IPFS Gateway is a Path Gateway. It exposes namespaces like `/ipfs/` and `/ipns/` under HTTP server root and provides basic primitives for integrating IPFS resources within existing HTTP stack. **Note:** additional Web Gateways aimed for website hosting and web browsers extend the below spec and are defined in :cite[subdomain-gateway] and :cite[dnslink-gateway]. There is also a minimal :cite[trustless-gateway] specification for use cases where client prefers to perform all validation locally. # HTTP API Path Gateway provides HTTP interface for requesting content-addressed data at specified content path. ## `GET /ipfs/{cid}[/{path}][?{params}]` Downloads data at specified **immutable** content path. - `cid` – a valid content identifier ([CID](https://docs.ipfs.io/concepts/glossary#cid)) - `path` – optional path parameter pointing at a file or a directory under the `cid` content root - `params` – optional query parameters that adjust response behavior ## `HEAD /ipfs/{cid}[/{path}][?{params}]` Same as GET, but does not return any payload. Implementations SHOULD limit the scope of IPFS data transfer triggered by `HEAD` requests to a minimal DAG subset required for producing response headers such as [`X-Ipfs-Roots`](#x-ipfs-roots-response-header), [`Content-Length`](#content-length-response-header) and [`Content-Type`](#content-type-response-header). ### only-if-cached HEAD behavior HTTP client can send `HEAD` request with [`Cache-Control: only-if-cached`](#cache-control-request-header) to disable IPFS data transfer and inexpensively probe if the gateway has the data cached. Implementation MUST ensure that handling `only-if-cached` `HEAD` response is fast and does not generate any additional I/O such as IPFS data transfer. This allows light clients to probe and prioritize gateways which already have the data. ## `GET /ipns/{name}[/{path}][?{params}]` Downloads data at specified **mutable** content path. Implementation must resolve the `name` to a CID, then serve response behind a `/ipfs/{resolved-cid}[/{path}][?{params}]` content path. - `name` may refer to: - cryptographic [IPNS key hash](https://docs.ipfs.io/concepts/glossary/#ipns) - human-readable DNS name with [DNSLink](https://docs.ipfs.io/concepts/glossary/#dnslink) set-up ## `HEAD /ipns/{name}[/{path}][?{params}]` Same as GET, but does not return any payload. # HTTP Request ## Request Headers All request headers are optional. ### `If-None-Match` (request header) Used for HTTP caching. Enables advanced cache control based on `Etag`, allowing client and server to skip data transfer if previously downloaded payload did not change. The Gateway MUST compare Etag values sent in `If-None-Match` with `Etag` that would be sent with response. Positive match MUST return HTTP status code 304 (Not Modified), without any payload. ### `Cache-Control` (request header) Used for HTTP caching. #### `only-if-cached` Client can send `Cache-Control: only-if-cached` to request data only if the gateway already has the data (e.g. in local datastore) and can return it immediately. If data is not cached locally, and the response requires an expensive remote fetch, a [`412 Precondition Failed`](#412-precondition-failed) HTTP status code should be returned by the gateway without any payload or specific HTTP headers. NOTE: when processing a request for a DAG, traversing it and checking every CID might be too expensive. Implementations SHOULD implement own heuristics to maximize cache hits while minimizing performance cost of checking if the entire DAG is locally cached. A good rule of thumb is to at the minimum test if the root block is in the local cache. ### `Accept` (request header) Can be used for requesting specific response format, and/or passing optional content type parameters. For example: - [application/vnd.ipld.raw](https://www.iana.org/assignments/media-types/application/vnd.ipld.raw) – disables [IPLD codec deserialization](https://ipld.io/docs/codecs/), requests a verifiable raw [block](https://docs.ipfs.io/concepts/glossary/#block) to be returned - [application/vnd.ipld.car](https://www.iana.org/assignments/media-types/application/vnd.ipld.car) – disables [IPLD codec deserialization](https://ipld.io/docs/codecs/), requests a verifiable [CAR](https://docs.ipfs.io/concepts/glossary/#car) stream to be returned with implicit or explicit [`dag-scope`](https://specs.ipfs.tech/http-gateways/trustless-gateway/#dag-scope-request-query-parameter) for blocks at the terminus of the specified path and the blocks required to traverse path segments from root CID to the terminus. - [application/x-tar](https://en.wikipedia.org/wiki/Tar_(computing)) – returns a UnixFS tree (files and directories) as a [TAR](https://en.wikipedia.org/wiki/Tar_(computing)) stream. Returned tree starts at a DAG which name is the same as the terminus segment. Produces 406 Not Acceptable for content that is not UnixFS. - [application/vnd.ipld.dag-json](https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-json) – Returns the block when CID codec is `dag-json`. Implementations MAY validate block data before returning. SHOULD produce 406 Not Acceptable when the CID codec does not match. - [application/vnd.ipld.dag-cbor](https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-cbor) – Returns the block when CID codec is `dag-cbor`. Implementations MAY validate block data before returning. SHOULD produce 406 Not Acceptable when the CID codec does not match. - [application/json](https://www.iana.org/assignments/media-types/application/json) – For blocks with CID codec `json`, returns block data as `application/json`. Implementations MAY validate block data before returning. For deserialized UnixFS files that represent text files with valid JSON, implementations SHOULD allow serving the file content as `application/json` regardless of the CID codec being `dag-pb` or `raw`. SHOULD produce 406 Not Acceptable in all other cases. - [application/cbor](https://www.iana.org/assignments/media-types/application/cbor) – Returns the block when CID codec is `cbor`. Implementations MAY validate block data before returning. SHOULD produce 406 Not Acceptable when the CID codec does not match. - [application/vnd.ipfs.ipns-record](https://www.iana.org/assignments/media-types/application/vnd.ipfs.ipns-record) – requests a verifiable :cite[ipns-record] to be returned. Produces 400 Bad Request if the content is not under the IPNS namespace, or contains a path. - [text/html](https://html.spec.whatwg.org/) – returns a human-readable representation of the requested data which may include a link to download the raw data. :::note A Client SHOULD include the [`format` query parameter](#format-request-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. ::: ### `Range` (request header) `Range` can be used for requesting specific byte range of UnixFS files and raw blocks. Gateway implementations SHOULD be smart enough to require only the minimal DAG subset necessary for handling the range request. Gateways SHOULD support single range requests. The support of more than one range is optional: implementation MAY decide to not support more than one range. :::note For more advanced use cases such as partial DAG/CAR streaming, or non-UnixFS data structures, see `dag-scope` and `entity-bytes` from :cite[ipip-0402] and ordered CARs from :cite[ipip-0412]. ::: ### `Service-Worker` (request header) Mentioned here for security reasons and should be implemented with care. This header is sent by web browser attempting to register a service worker script for a specific scope. Allowing too broad scope can allow a single content root to take control over gateway endpoint. It is important for implementations to handle this correctly. Service Worker should only be allowed under specific to content roots under `/ipfs/{cid}/` and `/ipns/{name}/` (IMPORTANT: note the trailing slash). Gateway should refuse attempts to register a service worker for entire `/ipfs/cid` or `/ipns/name` (IMPORTANT: when trailing slash is missing). Requests to these paths with `Service-Worker: script` MUST be denied by returning HTTP 400 Bad Request error. ## Request Query Parameters All query parameters are optional. ### `filename` (request query parameter) Optional, can be used for overriding the filename. When set, gateway will include it in `Content-Disposition` header and may use it for `Content-Type` calculation. Example: ``` https://ipfs.io/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG?filename=hello_world.txt ``` ### `download` (request query parameter) Optional, can be used to request specific `Content-Disposition` to be set on the response. Response to HTTP request with `download=true` MUST include `Content-Disposition: attachment[;filename=...]` to indicate that client should not render the response. The `attachment` context will force user agents such as web browsers to present a 'Save as' dialog instead (prefilled with the value of the `filename` parameter, if present) ### `format` (request query parameter) Optional, `format=` can be used to request specific response format. This is a URL-friendly alternative to sending an [`Accept`](#accept-request-header) header. These are the equivalents: - `format=raw` → `Accept: application/vnd.ipld.raw` - `format=car` → `Accept: application/vnd.ipld.car` - `format=tar` → `Accept: application/x-tar` - `format=dag-json` → `Accept: application/vnd.ipld.dag-json` - `format=dag-cbor` → `Accept: application/vnd.ipld.dag-cbor` - `format=json` → `Accept: application/json` - `format=cbor` → `Accept: application/cbor` - `format=ipns-record` → `Accept: 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. ::: A Gateway SHOULD include the [`Content-Location`](#content-location-response-header) header in the response when: - the request contains an `Accept` header specifying a well-known response format, but the URL does not include the `format` query parameter - the `format` parameter is present, but does not match the format from `Accept` ### `dag-scope` (request query parameter) Optional, can be used to limit the scope of verifiable DAG requests such as CAR, same as :ref[dag-scope] from :cite[trustless-gateway]. ### `entity-bytes` (request query parameter) Optional, can be used to limit the scope of verifiable DAG requests such as CAR, same as :ref[entity-bytes] from :cite[trustless-gateway]. ### `car-version` (request query parameter) Optional, specific to CAR requests, same as :ref[car-version] from :cite[trustless-gateway]. ### `car-order` (request query parameter) Optional, specific to CAR requests, same as :ref[car-order] from :cite[trustless-gateway]. ### `car-dups` (request query parameter) Optional, specific to CAR requests, same as :ref[car-dups] from :cite[trustless-gateway]. # HTTP Response ## Response Status Codes ### `200` OK The request succeeded. If the HTTP method was `GET`, then data is transmitted in the message body. If the HTTP method was `HEAD`, then no body should be sent. ### `206` Partial Content Partial Content: range request succeeded. Returned when requested range of data described by [`Range`](#range-request-header) header of the request. ### `301` Moved Permanently Indicates permanent redirection. The new, canonical URL is returned in the [`Location`](#location-response-header) header. ### `400` Bad Request A generic client error returned when it is not possible to return a better one. For example, this can be used when the CID is malformed or its codec is unsupported. ### `404` Not Found Error to indicate that request was formally correct but either: * traversal of the requested content path was not possible due to a invalid or missing DAG node, or * the requested content is not retrievable from this gateway. Gateways MUST use 404 to signal that content is not available, particularly when the gateway is [non recursive](#recursive-vs-non-recursive-gateways), and only provides access to a known dataset, so that it can assess that the requested content is not part of it. NOTE: Gateways MUST return 404 for missing root blocks. However, for streaming responses (such as CAR), once HTTP 200 OK status is sent, gateways cannot change it. If a child block is missing during streaming, the gateway SHOULD terminate the stream. Clients MUST verify response completeness. ### `406` Not Acceptable Returned when the requested response format does not match the CID's codec and the gateway does not perform cross-codec conversion. For example, requesting `?format=dag-json` on a `dag-cbor` block, or `?format=dag-cbor` on a `dag-pb` block, SHOULD return a 406 response. Similarly, requesting `?format=tar` for content that is not UnixFS SHOULD return 406. Implementations MAY include an actionable hint in the response body (e.g., suggesting the client fetch the raw block with `?format=raw` and convert client-side). See :cite[ipip-0524] for details. ### `410` Gone Error to indicate that request was formally correct, but this specific Gateway refuses to return requested data even though it would have normally provided it. `410` is particularly useful to implement [deny lists](#denylists), in order to not serve blocked content. The name of deny list and unique identifier of blocked entries can be provided in the response body. See: [Denylists](#denylists) See also: [`451 Unavailable for Legal Reasons`](#451-unavailable-for-legal-reasons). ### `412` Precondition Failed Error to indicate that request was formally correct, but Gateway is unable to return requested data under the additional (usually cache-related) conditions sent by the client. #### Use with only-if-cached - Client sends a request with [`Cache-Control: only-if-cached`](#cache-control-request-header) - Gateway does not have requested CIDs in local datastore, and is unable to fetch them from other peers due to `only-if-cached` condition - Gateway returns status code `412` to the client - The code 412 is used instead of 504 because `only-if-cached` is handled by the gateway itself, moving the error to client error range and avoiding confusing server errors in places like the browser console. ### `429` Too Many Requests Error to indicate the client has sent too many requests in a given amount of time. This error response SHOULD include [`Retry-After`](#retry-after-response-header) HTTP header to indicate how long the client should wait before making a follow-up request. ### `451` Unavailable For Legal Reasons Error to indicate that request was formally correct, but this specific Gateway is unable to return requested data due to legal reasons. Response SHOULD include an explanation, as noted in Section 3 of :cite[rfc7725]. See: [Denylists](#denylists) ### `500` Internal Server Error A generic server error returned when it is not possible to return a better one. An internal server error signals the general unavailability of the gateway. ### `502` Bad Gateway Error that indicates that a Gateway was not able to produce response for a known reason: for example, in the case of [recursive gateways](#recursive-vs-non-recursive-gateways), in the event of failure to find any providers for requested data. `502` indicates that the request can be retried and is not a permanent failure. This error response SHOULD include [`Retry-After`](#retry-after-response-header) HTTP header to indicate how long the client should wait before retrying. Gateways SHOULD return `404` instead of `502` when the content is known to be unretrievable: for example, when the Gateway is [non-recursive](#recursive-vs-non-recursive-gateways) and the content is known to not be available. ### `504` Gateway Timeout Error that indicates that the Gateway was not able to produce response under set time limits: for example, when gateway failed to retrieve data from a remote provider. `504` indicates that the request can be retried and is not a permanent failure. There is no generic timeout, Gateway implementations SHOULD set timeouts based on specific use cases. This error response SHOULD include [`Retry-After`](#retry-after-response-header) HTTP header to indicate how long the client should wait before retrying. Gateways SHOULD return `404` instead of `504` when the content is known to be unretrievable: for example, when the Gateway is [non-recursive](#recursive-vs-non-recursive-gateways) and the content is known to not be available. ## Response Headers ### `Etag` (response header) Used for HTTP caching. An opaque identifier for a specific version of the returned payload. The unique value must be wrapped by double quotes as noted in Section 8.8.3 of :cite[rfc9110]. In many cases it is not enough to base `Etag` value on requested CID. To ensure `Etag` is unique enough to avoid issues with caching reverse proxies and CDNs, implementations should base it on both CID and response type: - By default, etag should be based on requested CID. Example: `Etag: "bafy…foo"` - If a custom `format` was requested (such as a raw block, CAR), the returned etag should be modified to include it. It could be a suffix. - Example: `Etag: "bafy…foo.raw"` - If HTML directory index was generated by the gateway, the etag returned with HTTP response should be based on the version of gateway implementation. This is to ensure proper cache busting if code responsible for HTML generation changes in the future. - Example: `Etag: "DirIndex-2B423AF_CID-bafy…foo"` - When a gateway can’t guarantee byte-for-byte identical responses, a “weak” etag should be used. - Example: If CAR is streamed, and blocks arrive in non-deterministic order, the response should have `Etag: W/"bafy…foo.car"`. - Example: If TAR stream is generated by traversing an UnixFS directory in non-deterministic order, the response should have `Etag: W/"bafy…foo.x-tar"`. - When responding to [`Range`](#range-request-header) request, a strong `Etag` should be based on requested range in addition to CID and response format: `Etag: "bafy..foo.0-42` ### `Cache-Control` (response header) Used for HTTP caching. An explicit caching directive for the returned response. Informs HTTP client and intermediate middleware caches such as CDNs if the response can be stored in caches. Returned directive depends on requested content path and format: - `Cache-Control: public, max-age=29030400, immutable` MUST be returned for every immutable resource under `/ipfs/` namespace. - `Cache-Control: public, max-age=` SHOULD be returned for mutable resources under `/ipns/{id-with-ttl}/` namespace; `max-age=` SHOULD indicate remaining TTL of the mutable pointer such as :cite[ipns-record] or DNSLink TXT record. - Implementations MAY place an upper bound on any TTL received, as noted in Section 8 of :cite[rfc2181]. - If TTL value is unknown, implementations SHOULD not send a `Cache-Control` - No matter if TTL value is known or not, implementations SHOULD always send a [`Last-Modified`](#last-modified-response-header) header with the timestamp of the record resolution. ### `Last-Modified` (response header) Optional, used as additional hint for HTTP caching. Returning this header depends on the information available: - The header can be returned with `/ipns/` responses when the gateway implementation knows the exact time a mutable pointer was updated by the publisher. - When only TTL is known, [`Cache-Control`](#cache-control-response-header) should be used instead. - Legacy implementations set this header to the current timestamp when reading TTL on `/ipns/` content paths was not available. This hint was used by web browsers in a process called "Calculating Heuristic Freshness" (Section 4.2.2 of :cite[rfc9111]). Each browser uses different heuristic, making this an inferior, non-deterministic caching strategy. - New implementations should not return this header if TTL is not known; providing a static expiration window in `Cache-Control` is easier to reason about than cache expiration based on the fuzzy “heuristic freshness”. ### `Content-Type` (response header) Returned with custom response formats such as `application/vnd.ipld.car` or `application/vnd.ipld.raw`. CAR must be returned with explicit version. Example: `Content-Type: application/vnd.ipld.car; version=1` When deserialized responses are enabled, and no explicit response format is provided with the request, and the requested data itself has no built-in content type metadata, implementations SHOULD perform content type sniffing based on file name (from :ref[url] path, or optional [`filename`](#filename-request-query-parameter) parameter) and magic bytes to improve the utility of produced responses. For example: - detect plain text file and return `Content-Type: text/plain` instead of `application/octet-stream` - detect SVG image and return `Content-Type: image/svg+xml` instead of `text/xml` ### `Content-Disposition` (response header) Returned when `download`, `filename` query parameter, or a custom response `format` such as `car` or `raw` block are used. The first parameter passed in this header indicates if content should be displayed `inline` by the browser, or sent as an `attachment` that opens the “Save As” dialog: - `Content-Disposition: inline` is the default, returned when request was made with `download=false` or a custom `filename` was provided with the request without any explicit `download` parameter. - `Content-Disposition: attachment` is returned only when request was made with the explicit `download=true` The remainder is an optional `filename` parameter that will be prefilled in the “Save As” dialog. NOTE: when the `filename` includes non-ASCII characters, the header must include both ASCII and UTF-8 representations for compatibility with legacy user agents and existing web browsers. To illustrate, `?filename=testтест.pdf` should produce: `Content-Disposition inline; filename="test____.jpg"; filename*=UTF-8''test%D1%82%D0%B5%D1%81%D1%82.jpg` - ASCII representation must have non-ASCII characters replaced with `_` - UTF-8 representation must be wrapped in Percent Encoding (Section 2.1 of :cite[rfc3986]). - NOTE: `UTF-8''` is not a typo – see Section 3.2.3 of :cite[rfc8187]. `Content-Disposition` must be also set when a binary response format was requested: - `Content-Disposition: attachment; filename=".car"` should be returned with `Content-Type: application/vnd.ipld.car` responses to ensure client does not attempt to render streamed bytes. CID and `.car` file extension should be used if a custom `filename` was not provided with the request. - `Content-Disposition: attachment; filename=".bin"` should be returned with `Content-Type: application/vnd.ipld.raw` responses to ensure client does not attempt to render raw bytes. CID and `.bin` file extension should be used if a custom `filename` was not provided with the request. ### `Content-Location` (response header) Returned when a non-default content format has been negotiated with the [`Accept` header](#accept-request-header) but `format` was missing from the URL. The value of this field SHOULD include the URL of the resource with the `format` query parameter included, so that generic HTTP caches can store deserialized, CAR, and block responses separately. :::note For example, a request to `/ipfs/{cid}` with `Accept: application/vnd.ipld.raw` SHOULD return a `Content-Location: /ipfs/{cid}?format=raw` header in order for block response to be cached separately from deserialized one. ::: ### `Content-Length` (response header) Represents the length of returned HTTP payload. :::warning NOTE: the value may differ from the real size of requested data if compression or chunked `Transfer-Encoding` are used. See [ipfs/specs#461](https://github.com/ipfs/specs/issues/461). ::: ### `Content-Range` (response header) Returned only when request was a [`Range`](#range-request-header) request. See Section 14.4 of :cite[rfc9110]. ### `Accept-Ranges` (response header) Optional, returned to explicitly indicate if gateway supports partial HTTP [`Range`](#range-request-header) requests for a specific resource. For example, `Accept-Ranges: none` should be returned with `application/vnd.ipld.car` responses if the block order in CAR stream is not deterministic. ### `Location` (response header) Returned only when response status code is [`301` Moved Permanently](#301-moved-permanently). The value informs the HTTP client about new URL for requested resource. #### Use in directory URL normalization Gateway MUST return a redirect when a valid UnixFS directory was requested without the trailing `/`, for example: - response for UnixFS directory at `https://example.com/ipns/en.wikipedia-on-ipfs.org/wiki` (no trailing slash) MUST be HTTP 301 redirect with `Location: /ipns/en.wikipedia-on-ipfs.org/wiki/` This normalization is required for directory-based relative links and security scopes such as Service Worker registrations to work correctly. It also ensures the same behavior on path gateways (`https://example.com/ipfs/cid/` with trailing `/`) and origin-isolated HTTP contexts `https://cid.ipfs.dweb.link` or non-HTTP URLs like `ipfs://cid`, where empty path component is implicit `/`. #### Use in interop with Subdomain Gateway See [`Location` section](https://specs.ipfs.tech/http-gateways/subdomain-gateway/#location-response-header) of :cite[subdomain-gateway]. ### `Ipfs-Uri` (response header) Used for HTTP caching and indicating the IPFS address of the data. Indicates the original, requested content path before any path resolution and traversal is performed. The value is one valid URI: `ipfs://` for the `/ipfs/` namespace (:cite[ipfs-uri]), or `ipns://` for `/ipns/` (:cite[ipns-uri]). The content path is the request path, normalized: percent-decode each segment once, collapse duplicate slashes, then apply any `.` and `..` segments. `%2F` decodes to a separator in the first step, so a request for `/ipfs/{cid}/a%2F..%2Fb.txt` gives the content path `/ipfs/{cid}/b.txt`. One consequence: a UnixFS link whose name contains `/` cannot be addressed by any content path, because path components never contain `/` (:cite[unixfs]); such an entry is reachable only by its own CID. The URI authority SHOULD be the content root in the canonical form :cite[ipfs-uri] and :cite[ipns-uri] define. A trailing dot on a DNS name is stripped. Under `/ipns/`, a legacy base58 peer ID (`Qm...`, `12D3Koo...`) becomes a `libp2p-key` CIDv1 in base36. A gateway omits this header when it cannot produce such an authority: the root is invalid, the IPNS key uses a codec it does not support, or the DNS name does not convert to the `dnslink-name` form. In the DNSLink case, a name with no dot, like `examplemissingtld`, never becomes an authority: on each network it can point at different content. A dotted DNSLink name on a private network, like `example.local`, is fine. A content root longer than 63 characters is emitted in full and HTTP clients SHOULD accept it: the length guidance in :cite[ipfs-uri] and :cite[ipns-uri] does not apply to this header. The URI path mirrors the rest of the content path: split it on `/`, percent-encode each segment, and rejoin with `/`. A trailing slash is kept, and an empty remainder means no path: `/ipfs/{cid}` → `ipfs://{cid}`, `/ipfs/{cid}/` → `ipfs://{cid}/`, `/ipfs/{cid}/dir/` → `ipfs://{cid}/dir/`. Interior empty segments MUST NOT appear. Each segment MUST be percent-encoded over its UTF-8 bytes: every byte outside the unreserved set `A-Z a-z 0-9 - . _ ~` (Section 2.3 of :cite[rfc3986]) MUST be encoded as `%XX` with uppercase hexadecimal digits, and unreserved bytes MUST NOT be encoded. This covers `%`, `?`, `#`, spaces, control characters, and every non-ASCII byte, so the value is ASCII-only and always a valid field value (Section 5.5 of :cite[rfc9110]). The one exception: a segment that is exactly `.` or `..` MUST be emitted as `%2E` or `%2E%2E`, so it cannot be read as a dot segment. The normalization above means such segments do not normally occur. The value MUST NOT contain a query or fragment. Request query parameters such as `?format=car` never appear in it. An HTTP client that recovers the content path MUST apply the URI-to-content-path mapping of :cite[ipfs-uri] and :cite[ipns-uri], then percent-decode each segment once. Example: `Ipfs-Uri: ipfs://bafy..ul6/subdir/file.txt` Example: `Ipfs-Uri: ipns://dnslink.example.net/wiki/Bogot%C3%A1` This header SHOULD be returned with deserialized responses, and on redirect and error responses once the content root has been parsed. Implementations MAY omit it with trustless response types (`application/vnd.ipld.raw` and `application/vnd.ipld.car`). They MAY also omit it when the value would exceed 8192 bytes. This header supersedes the deprecated [`X-Ipfs-Path`](#x-ipfs-path-response-header). HTTP clients SHOULD prefer `Ipfs-Uri` when both headers are present. Gateways serving cross-origin traffic SHOULD include `Ipfs-Uri` in `Access-Control-Expose-Headers`. ### `X-Ipfs-Path` (response header) :::warning This header is deprecated. HTTP header values use a more limited character set than IPFS Paths so it is possible to include characters that may break some HTTP Clients. The `Ipfs-Uri` response header should be used instead if it is present as it can correctly encode all characters that might be found in an IPFS Path. ::: Used for HTTP caching and indicating the IPFS address of the data. Indicates the original, requested content path before any path resolution and traversal is performed. Example: `X-Ipfs-Path: /ipns/k2..ul6/subdir/file.txt` Gateways MAY keep returning this header for legacy clients, with the value format unchanged. New implementations SHOULD return only [`Ipfs-Uri`](#ipfs-uri-response-header). A gateway that returns this header MUST omit it when the content path contains any byte other than HTAB (0x09), SP (0x20), or visible ASCII (0x21-0x7E). Gateways that return this header SHOULD do so with deserialized responses. Implementations MAY omit it with trustless response types (`application/vnd.ipld.raw` and `application/vnd.ipld.car`). Gateways that return this header for cross-origin traffic SHOULD also include it in `Access-Control-Expose-Headers`. ### `X-Ipfs-Roots` (response header) Used for HTTP caching. A way to indicate all CIDs required for resolving logical roots (path segments) from the requested content path. The main purpose of this header is allowing HTTP caches to make smarter decisions about cache invalidation. Below, an example to illustrate how `X-Ipfs-Roots` is constructed from a content path pointing at a DNSLink. The traversal of `/ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey` includes a HAMT-sharded UnixFS directory `/wiki/`. This header only cares about logical roots (one per URL path segment): 1. `/ipns/en.wikipedia-on-ipfs.org` → `bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze` 2. `/ipns/en.wikipedia-on-ipfs.org/wiki/` → `bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4` 3. `/ipns/en.wikipedia-on-ipfs.org/wiki/Block_of_Wikipedia_in_Turkey` → `bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma` Final array of roots: ``` X-Ipfs-Roots: bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze,bafybeihn2f7lhumh4grizksi2fl233cyszqadkn424ptjajfenykpsaiw4,bafkreibn6euazfvoghepcm4efzqx5l3hieof2frhp254hio5y7n3hv5rma ``` NOTE: while the first CID will change every time any article is changed, the last root (responsible for specific article or a subdirectory) may not change at all, allowing for smarter caching beyond what standard Etag offers. This header SHOULD be returned with deserialized responses. Implementations MAY omit it with trustless response types (`application/vnd.ipld.raw` and `application/vnd.ipld.car`). NOTE: Gateways that stream responses (e.g., CAR) without pre-resolving the entire path MAY only include the root CID for simple `/ipfs/{cid}` requests, or MAY omit this header for path requests where intermediate CIDs are not known when headers are sent. ### `X-Content-Type-Options` (response header) Optional, present in certain response types: - `X-Content-Type-Options: nosniff` should be returned with `application/vnd.ipld.car` and `application/vnd.ipld.raw` responses to indicate that the [`Content-Type`](#content-type-response-header) should be followed and not be changed. This is a security feature, ensures that non-executable binary response types are not used in `