FISL — Finding Internet Structures & Links

date2026-09-09
status
editorsDaniel Norman <daniel@norman.life>
issueslist, new
abstract

FISL is an HTTP lookup API that maps CIDs to locations. A client sends a CID and receives the URLs at which the content can be retrieved, together with the identity of the publisher that announced them, when it is known. Datadisco defines one way to build and maintain the index behind the API; FISL does not depend on how the index is built.

Introduction

Content-addressed resources are self-certifying: a client that knows a CID can verify the bytes it receives, no matter who serves them ([ipfs-principles]). This leaves one problem open: given a CID, where do you fetch it from?

RASL ([rasl]) answers this with inline hints, but hints are static: they are baked into the URL at the time of writing and go stale. FISL makes discovery dynamic. A client asks an indexer at the moment of retrieval, and the answer reflects what publishers currently announce.

FISL specifies only the query interface. How an indexer collects, validates, and expires location records is out of scope. Datadisco ([[datadisco]]) specifies a publication and indexing layer on the AT Protocol whose index FISL can serve directly.

Locations

A FISL record states one set of retrieval locations for the requested CID, and who announced them, if the indexer knows. Its fields are:

Each addr object has the fields:

The record's expires is a ceiling: the effective expiry of an addr is the earliest of its own expires and the record's, whichever of the two are present. An addr without its own expires inherits the record's. An addr with neither has no stated expiry. An absent expires states no expiry; it does not mean that the record or the addr is expired.

Indexers should not return a record after its expires, and should not return an addr after its effective expiry. Clients should check these times and ignore records and addrs that are expired. A record with no stated expiry is retired only by the indexer's own freshness policy, which is out of scope of this specification. expires is a publisher's claim about validity, not the mechanism that keeps the index live.

A datetime is a string that should meet the intersecting requirements of the RFC 3339, ISO 8601, and WHATWG HTML datetime standards, as specified for the AT Protocol Lexicon datetime type ([lexicon]).

Entries and addr objects may carry additional fields. Clients must ignore fields they do not recognize.

Lookup API

An indexer answers lookups at:

GET /routing/locations/{cid}/{format}
      

Responses:

Response formats

The format is named by the path, not negotiated. An indexer must support both formats, and states the format of the body in the Content-Type response header:

  • json (application/json): a JSON object with a locations array.
  • jsonl (application/jsonl): JSON Lines ([jsonl]) one location record per line, each a JSON object followed by a newline, with no wrapper object.

Indexers must not select the format with the Accept request header. A format added by a later revision of this specification takes its own path segment, and an indexer that does not implement it responds with 404.

JSON

The json body is a JSON object. A CID with no live records returns {"locations": []}.

{
  "locations": [
    {
      "publisher": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
      "addrs": [
        {
          "url": "https://berjon.com/.well-known/rasl/bafkreifn5yxi7nkftsn46b6x26grda57ict7md2xuvfbsgkiahe2e7vnq4"
        },
        { "url": "https://mirror.example.com/kitten.jpg", "expires": "2026-09-01T00:00:00Z" }
      ],
      "size": 94201,
      "expires": "2026-09-18T00:00:00Z"
    }
  ]
}
          

JSONL

The JSONL body contains one location record per line. A CID with no live records returns an empty body with status 200.

{"publisher":"did:plc:ewvi7nxzyoun6zhxrhs64oiz","addrs":[{"url":"https://berjon.com/.well-known/rasl/bafkreifn5yxi7nkftsn46b6x26grda57ict7md2xuvfbsgkiahe2e7vnq4"}],"size":94201,"expires":"2026-09-18T00:00:00Z"}
{"publisher":"did:web:mirror.example.com","addrs":[{"url":"https://mirror.example.com/kitten.jpg","expires":"2026-08-30T00:00:00Z"}],"expires":"2026-09-01T00:00:00Z"}
{"addrs":[{"url":"https://cache.example.net/kitten.jpg"}]}
          

JSONL exists for streaming. An indexer can write each record as it finds it, for example when the index is sharded across backends, or when entries are relayed from a live subscription, instead of buffering the full set. Clients must parse lines incrementally and must not assume a bound on the number of entries.

Caching

Responses should carry a Cache-Control: public, max-age={ttl} header, where ttl does not exceed the time until the earliest expiry in the response. When no record in the response states an expiry, the indexer selects ttl itself and should keep it short.

Indexers may also send stale-while-revalidate and stale-if-error ([rfc5861]). Both headers let a cache serve a stored response after its max-age ends. The full time in which a cache can answer is therefore max-age plus that value, and that total must not exceed the time until the earliest expiry:

max-age + stale-while-revalidate <= time to earliest expiry
        

If the total exceeds it, a cache answers with records that the client then ignores as expired. The lookup succeeds, but it gives the client no location to try.

Within that bound, a stale answer costs the client little: it makes one failed attempt and then tries another addr. These extensions therefore let an indexer keep max-age short and still answer most requests from cache.

Empty results should use a short max-age (e.g. 5 seconds) and no stale-while-revalidate. A stale empty response leaves a client with no location to try at all.

CORS

Indexers must support CORS so browser clients on any origin can query them:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
        

Retrieval

FISL only does discovery. To retrieve, a client picks the URLs it supports, fetches them in whatever order it prefers, and verifies that the received bytes hash to the requested CID; on mismatch discarding the response and trying another location.

For an https URL, retrieval follows steps 2 through 8 of the steps to fetch a RASL URL ([rasl]), with the URL used as the request URL directly instead of constructing one from a hint: a stateless GET with no content negotiation, redirects treated as 307, the response treated as application/octet-stream, and the bytes verified against the CID. A RASL server therefore needs no special marking in a location entry — its /.well-known/rasl/{cid} URL is announced as an ordinary https URL.

For BDASL CIDs, clients may use streaming verification ([bdasl]) instead of whole-content hashing.

Transport agnosticity

An addr is a URL and nothing more. The scheme names the transport, so the transport is encapsulated by the URL and never appears as a separate field. A new transport needs a scheme and a specification of that scheme. It does not need a new field, a registry at the indexer, or a new version of this API.

Indexers treat URLs opaquely. An indexer must not drop, rewrite, or reject an addr because it does not know the scheme; it returns what publishers announce. Selection is the client's job: a client uses the schemes it supports and skips the rest.

A scheme specification states how a client gets the bytes for a CID from the URL. Two shapes are common:

A URL does not have to carry network coordinates. radiroh:// names an endpoint identity only and leaves resolution to the client. A scheme that named a RASL server instead of a byte location would fit the same model: the client would build the /.well-known/rasl/{cid} URL from the CID it looked up ([rasl]).

Whatever the scheme, the client checks that the bytes hash to the requested CID. Some transports verify as they stream: iroh-blobs with BLAKE3 ([blake3]), or BDASL over https ([bdasl]).

An entry may mix transports. A client picks whichever addr it can use:

{
  "publisher": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
  "addrs": [
    {
      "url": "https://berjon.com/.well-known/rasl/bafkreifn5yxi7nkftsn46b6x26grda57ict7md2xuvfbsgkiahe2e7vnq4"
    },
    { "url": "radiroh://25igmk4u75iqsbosfvep5sxoozazizgoxfnuqzsnl22pbddig5ra" }
  ],
  "expires": "2026-09-18T00:00:00Z"
}
      

Security Considerations

Relationship to IPFS Delegated Routing

This section is informational. The lookup API path and shape are modelled on the IPFS Delegated Routing V1 HTTP API ([routing-v1]) and its generic schema ([ipip-0518]), which motivated this design. FISL is not conformant with that API and does not intend to be: it renames the providers path segment and response key to locations and the ID field to publisher, it drops the Schema and Protocols fields, multiaddr support, and libp2p peer records, it names the response format in the path instead of negotiating it with Accept, and it adds optional expiry.

References

[bdasl]
Robin Berjon, Brendan O'Brien, & Juan Caballero. Big DASL (BDASL). 2026-09-09. URL: https://dasl.ing/bdasl.html
[blake3]
J-P. Aumasson, S. Neves, J. O'Connor, Z. Wilcox. The BLAKE3 Hashing Framework. July 2024. URL: https://www.ietf.org/archive/id/draft-aumasson-blake3-00.html
[cid]
Robin Berjon & Juan Caballero. Content IDs (CIDs). 2026-09-09. URL: https://dasl.ing/cid.html
[did-core]
M. Sporny, D. Longley, M. Sabadello, D. Reed, O. Steele, C. Allen. Decentralized Identifiers (DIDs). W3C Recommendation, July 2022. URL: https://www.w3.org/TR/did-core/
[did-key]
D. Longley, D. Zagidulin, M. Sporny. The did:key Method. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/did-method-key/
[did-plc]
B. Newbold, D. Holmgren, J. Gold, D. Olszewski. DID PLC Method (did:plc). URL: https://web.plc.directory/spec/v0.1/did-plc
[did-web]
did:web Method Specification. W3C Credentials Community Group. URL: https://w3c-ccg.github.io/did-method-web/
[ipfs-principles]
Robin Berjon. IPFS Principles. march 2023. URL: https://specs.ipfs.tech/architecture/principles/
[ipip-0518]
IPIP-0518. URL: https://github.com/ipfs/specs/pull/518
[iroh-blobs]
iroh-blobs protocol specification
[jsonl]
JSON Lines. URL: https://jsonlines.org/
[lexicon]
AT Protocol: Lexicon.
[radiroh]
The radiroh URI scheme. URL: https://radicle.network/nodes/radicle.norman.life/rad:z4VYyJ9KuwMNkXGQnmKuGPGKw3inv/tree/docs/uri-scheme.md
[rasl]
Robin Berjon & Juan Caballero. RASL — Retrieval of Arbitrary Structures & Links. 2026-09-09. URL: https://dasl.ing/rasl.html
[rfc5861]
M. Nottingham. HTTP Cache-Control Extensions for Stale Content. May 2010. URL: https://www.rfc-editor.org/rfc/rfc5861
[routing-v1]
Delegated Routing V1 HTTP API. IPFS Specifications. URL: https://specs.ipfs.tech/routing/http-routing-v1/
[url]
WHATWG. URL. Living Standard. URL: https://url.spec.whatwg.org/