# FISL — Finding Internet Structures & Links

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](#ref-ipfs-principles)\]). This leaves one problem open: given a CID, where do you fetch it from?

RASL (\[[rasl](#ref-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](#ref-did-core)\]. Any DID method is allowed, for example `did:key` (\[[did-key](#ref-did-key)\]), `did:plc` (\[[did-plc](#ref-did-plc)\]), or `did:web` (\[[did-web](#ref-did-web)\]). Indexers and clients must not reject a `publisher` because they do not know its method, and a client does not have to resolve it: FISL uses the `publisher` as an opaque identity. For an index built with Datadisco, this is the DID of the repository that published the record (\[\[datadisco\]\]). An indexer omits `publisher` when it has no publisher identity for the record. Clients must not treat a record as more trustworthy because it carries a `publisher`: 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](#dfn-datetime).

Each addr object has the fields:

-   `url` (required): an absolute URL (\[[url](#ref-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](#transport-agnosticity)). This specification defines retrieval for `https`, where the URL points directly at the bytes: a client dereferences it as-is and appends nothing (see [Retrieval](#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](#dfn-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 <dfn id="dfn-datetime">datetime</dfn> 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](https://atproto.com/specs/lexicon#datetime) type (\[[lexicon](#ref-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](#ref-cid)\], \[[bdasl](#ref-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: `json` or `jsonl`.

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](#ref-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 a `locations` array.
-   `jsonl` (`application/jsonl`): JSON Lines (\[[jsonl](#ref-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](#ref-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](https://dasl.ing/rasl.html#dfn-fetch-a-rasl-url) (\[[rasl](#ref-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](#ref-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.jpg` is used as-is (see [Retrieval](#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](#ref-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](#ref-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](#ref-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](#ref-blake3)\]), or BDASL over `https` (\[[bdasl](#ref-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 no `publisher`.
-   Clients should skip `http` URLs 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.
-   `size` is 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](#ref-routing-v1)\]) and its `generic` schema (\[[ipip-0518](#ref-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

<dfn id="ref-bdasl">\[bdasl\]</dfn>

Robin Berjon, Brendan O'Brien, & Juan Caballero. [Big DASL (BDASL)](https://dasl.ing/bdasl.html). 2026-09-09. URL: [https://dasl.ing/bdasl.html](https://dasl.ing/bdasl.html)

<dfn id="ref-blake3">\[blake3\]</dfn>

J-P. Aumasson, S. Neves, J. O'Connor, Z. Wilcox. [The BLAKE3 Hashing Framework](https://www.ietf.org/archive/id/draft-aumasson-blake3-00.html). July 2024. URL: [https://www.ietf.org/archive/id/draft-aumasson-blake3-00.html](https://www.ietf.org/archive/id/draft-aumasson-blake3-00.html)

<dfn id="ref-cid">\[cid\]</dfn>

Robin Berjon & Juan Caballero. [Content IDs (CIDs)](https://dasl.ing/cid.html). 2026-09-09. URL: [https://dasl.ing/cid.html](https://dasl.ing/cid.html)

<dfn id="ref-did-core">\[did-core\]</dfn>

M. Sporny, D. Longley, M. Sabadello, D. Reed, O. Steele, C. Allen. [Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/). W3C Recommendation, July 2022. URL: [https://www.w3.org/TR/did-core/](https://www.w3.org/TR/did-core/)

<dfn id="ref-did-key">\[did-key\]</dfn>

D. Longley, D. Zagidulin, M. Sporny. [The did:key Method](https://w3c-ccg.github.io/did-method-key/). W3C Credentials Community Group. URL: [https://w3c-ccg.github.io/did-method-key/](https://w3c-ccg.github.io/did-method-key/)

<dfn id="ref-did-plc">\[did-plc\]</dfn>

B. Newbold, D. Holmgren, J. Gold, D. Olszewski. [DID PLC Method (did:plc)](https://web.plc.directory/spec/v0.1/did-plc). URL: [https://web.plc.directory/spec/v0.1/did-plc](https://web.plc.directory/spec/v0.1/did-plc)

<dfn id="ref-did-web">\[did-web\]</dfn>

[did:web Method Specification](https://w3c-ccg.github.io/did-method-web/). W3C Credentials Community Group. URL: [https://w3c-ccg.github.io/did-method-web/](https://w3c-ccg.github.io/did-method-web/)

<dfn id="ref-ipfs-principles">\[ipfs-principles\]</dfn>

Robin Berjon. [IPFS Principles](https://specs.ipfs.tech/architecture/principles/). march 2023. URL: [https://specs.ipfs.tech/architecture/principles/](https://specs.ipfs.tech/architecture/principles/)

<dfn id="ref-ipip-0518">\[ipip-0518\]</dfn>

[IPIP-0518](https://github.com/ipfs/specs/pull/518). URL: [https://github.com/ipfs/specs/pull/518](https://github.com/ipfs/specs/pull/518)

<dfn id="ref-iroh-blobs">\[iroh-blobs\]</dfn>

[iroh-blobs protocol specification](https://docs.rs/iroh-blobs/latest/iroh_blobs/protocol/index.html)

<dfn id="ref-jsonl">\[jsonl\]</dfn>

[JSON Lines](https://jsonlines.org/). URL: [https://jsonlines.org/](https://jsonlines.org/)

<dfn id="ref-lexicon">\[lexicon\]</dfn>

[AT Protocol: Lexicon](https://atproto.com/specs/lexicon).

<dfn id="ref-radiroh">\[radiroh\]</dfn>

[The radiroh URI scheme](https://radicle.network/nodes/radicle.norman.life/rad:z4VYyJ9KuwMNkXGQnmKuGPGKw3inv/tree/docs/uri-scheme.md). URL: [https://radicle.network/nodes/radicle.norman.life/rad:z4VYyJ9KuwMNkXGQnmKuGPGKw3inv/tree/docs/uri-scheme.md](https://radicle.network/nodes/radicle.norman.life/rad:z4VYyJ9KuwMNkXGQnmKuGPGKw3inv/tree/docs/uri-scheme.md)

<dfn id="ref-rasl">\[rasl\]</dfn>

Robin Berjon & Juan Caballero. [RASL — Retrieval of Arbitrary Structures & Links](https://dasl.ing/rasl.html). 2026-09-09. URL: [https://dasl.ing/rasl.html](https://dasl.ing/rasl.html)

<dfn id="ref-rfc5861">\[rfc5861\]</dfn>

M. Nottingham. [HTTP Cache-Control Extensions for Stale Content](https://www.rfc-editor.org/rfc/rfc5861). May 2010. URL: [https://www.rfc-editor.org/rfc/rfc5861](https://www.rfc-editor.org/rfc/rfc5861)

<dfn id="ref-routing-v1">\[routing-v1\]</dfn>

[Delegated Routing V1 HTTP API](https://specs.ipfs.tech/routing/http-routing-v1/). IPFS Specifications. URL: [https://specs.ipfs.tech/routing/http-routing-v1/](https://specs.ipfs.tech/routing/http-routing-v1/)

<dfn id="ref-url">\[url\]</dfn>

WHATWG. [URL](https://url.spec.whatwg.org/). Living Standard. URL: [https://url.spec.whatwg.org/](https://url.spec.whatwg.org/)