FISLFinding Internet Structures & Links
An HTTP lookup that maps CIDs to locations. Send a CID, get back the URLs the content can be retrieved from and who announced them. This page explains it; the normative text is the specification.
The location record
One record states one set of retrieval locations for the requested CID, and who announced them if the indexer knows.
| Field | Presence | Meaning |
|---|---|---|
publisher |
optional | the identity of the publisher, as a string that matches the DID syntax of did-core. Any DID method is allowed, for example did:key (did-key), did:plc (did-plc), or did:web (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 atfisl, this is the DID of the repository that published the record (atfisl). 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 datetimeA 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).. |
The addr object
Each entry in addrs describes one location.
| Field | Presence | Meaning |
|---|---|---|
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 for https (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 datetimeA 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).. |
publisher is an identity, not a warrant. Clients must not
treat a record as more trustworthy because it carries one. Only CID verification proves the
bytes. It is useful for rate-limiting whoever keeps announcing locations that fail.
Expiry
The record's expires is a ceiling. An addr's effective expiry is the earliest
of its own and the record's, whichever are present; an addr without its own inherits the
record's; an addr with neither has no stated expiry.
expires states no expiry. Such
a record is retired by the indexer's own freshness policy, which the spec leaves open.
expires is a publisher's claim about validity, not the mechanism that keeps an
index live.
This lets one record mix a stable mirror with a short-lived one without re-publishing on the shortest lifetime's cadence.
Lookup API
GET /routing/locations/{cid}/{format}
{cid} is a DASL or BDASL CID in string form. DASL CIDs have exactly one valid
string encoding, so no normalization is needed and the path is maximally cacheable.
{format} is json or jsonl.
| Status | Presence | Meaning |
|---|---|---|
200 | always | Zero or more locations. A CID with no live records is not an error. |
400 | on bad input | {cid} is not a valid DASL or BDASL CID. |
404 | on bad input | {format} is not a format this indexer supports. |
Response formats
The format is named in the path, never negotiated with Accept. A format added by
a later revision takes its own path segment, and an indexer that does not implement it
answers 404.
{
"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"
}
]
}
{"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, rather than
buffering the full set. Clients must parse lines incrementally and must not assume a bound on
how many arrive. The empty answer is an empty body with status 200.
Caching
Responses should carry Cache-Control: public, max-age={ttl}, where
ttl does not exceed the time until the earliest expiry in the response. With
stale-while-revalidate, the bound covers the total:
max-age + stale-while-revalidate <= time to earliest expiry
Exceed it and a cache answers with records the client then discards as expired: the lookup
succeeds and gives the client nothing to try. Within the bound a stale answer is cheap —
one failed fetch, then the next addr — so an indexer can keep max-age short
and still serve most requests from cache.
max-age (5 seconds,
say) and no stale-while-revalidate. A stale empty response leaves a client with
no location to try at all.
CORS
Indexers must be queryable from a browser on any origin:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
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 it — not a new field, a registry, or a new version of this API.
Indexers treat URLs opaquely and must not drop, rewrite, or reject an addr whose scheme they do not know. Selection is the client's job. So one record can mix transports, and each client takes what 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"
}
A URL need not carry network coordinates. radiroh:// names an iroh endpoint
— a public key — and leaves resolution to the client; the CID is absent because the
client already holds it. One such URL covers every CID that endpoint serves.
https, FISL follows
RASL's fetch steps with the URL used directly. Some transports verify as they stream:
iroh-blobs with BLAKE3, or BDASL over https.