FISL — Finding Internet Structures & Links
| date | 2026-09-09 |
|---|---|
| status | |
| editors | Daniel Norman <daniel@norman.life> |
| issues | list, 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:
-
publisher(optional): the identity of the publisher, as a string that matches the DID syntax of [did-core]. Any DID method is allowed, for exampledid:key([did-key]),did:plc([did-plc]), ordid:web([did-web]). Indexers and clients must not reject apublisherbecause they do not know its method, and a client does not have to resolve it: FISL uses thepublisheras an opaque identity. For an index built with Datadisco, this is the DID of the repository that published the record ([[datadisco]]). An indexer omitspublisherwhen it has no publisher identity for the record. Clients must not treat a record as more trustworthy because it carries apublisher: only CID verification proves the bytes. -
addrs(required): an array of one or more addr objects, each describing one location at which the content can be retrieved. -
size(optional): the size of the content in bytes, as an integer. This is an unverified hint for clients to plan retrieval. -
expires(optional): the time after which this record is no longer valid, as a datetime.
Each addr object has the fields:
-
url(required): an absolute URL ([url]) from which the content can be retrieved. The scheme identifies the retrieval method, and with it how a client turns the URL and the CID into a request (see Transport agnosticity). This specification defines retrieval forhttps, where the URL points directly at the bytes: a client dereferences it as-is and appends nothing (see Retrieval). Clients must skip URLs whose schemes they do not support. -
expires(optional): the time after which this addr is no longer valid, as a datetime.
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}
-
{cid}is the DASL or BDASL CID to look up in string form ([cid], [bdasl]). Because DASL CIDs have exactly one valid string encoding, no normalization is needed and the path is maximally HTTP-cacheable. -
{format}is the response format:jsonorjsonl.
Responses:
-
200: the body contains zero or more locations. A CID with no live records is not an error. -
400:{cid}is not a valid DASL or BDASL CID ([bdasl]). -
404:{format}is not a format this indexer supports.
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 alocationsarray. -
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 location URL names a concrete retrieval location, e.g.,
https://mirror.example.com/kitten.jpgis used as-is (see Retrieval). -
An endpoint URL names a public key to which a connection can be
established and which serves the bytes for a given CID. For example,
radiroh://<endpoint-id>([radiroh]) names an iroh endpoint (a 32-byte public key) which is resolved to a relay address used to establish a direct connection, over which the client fetches over iroh-blobs ([iroh-blobs]). The CID is absent from the URL because the client already holds it: it is the CID it looked up. One such URL therefore covers every CID that endpoint serves.
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
-
Location entries are claims, not proofs. A publisher can announce URLs it does not control,
or that serve wrong bytes. Client-side CID verification makes this a denial-of-service
nuisance, not an integrity failure. Clients should rate-limit and de-prioritize locations
that fail verification, keyed by the record's
publisher, or by the URL origin when the record has nopublisher. -
Clients should skip
httpURLs unless explicitly configured otherwise, for testing and private networks. - A hostile entry can point at internal network addresses. Clients should guard against requests to private address space.
-
sizeis unverified. Clients must not allocate resources based on it without bounds. - Each transport brings its own risks. A client must skip a scheme it does not understand rather than guess at it, and must apply the checks of that transport; for example, an endpoint URL requires the client to apply those checks to the address that it resolves the endpoint id to.
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/