FISLFinding Internet Structures & Links

atfisl

Publishers announce where a CID can be fetched by writing dev.fisl.at records to their own AT Protocol repository. Indexers aggregate them and answer FISL lookups. This page explains it; the normative text is the specification.


The location record

One record announces that one CID is retrievable at one or more URLs. Records use the dev.fisl.at collection in the publisher's repository.

dev.fisl.at
{
  "$type": "dev.fisl.at",
  "cid": "bafkreifn5yxi7nkftsn46b6x26grda57ict7md2xuvfbsgkiahe2e7vnq4",
  "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"
}
FieldPresenceMeaning
cid required the CID being announced, as a string. It must be a valid DASL CID (cid) or BDASL CID (bdasl). No other CID formats are supported.
addrs required an array of one or more addr objects, each describing one location for the content.
size optional the size of the content in bytes, as an integer. This is a hint for clients to plan retrieval; it is not verified by indexers.
expires optional the time after which this record is no longer valid, as an AT Protocol Lexicon datetime string, which should meet the intersecting requirements of the RFC 3339, ISO 8601, and WHATWG HTML datetime standards (lexicon). Publishers that want to keep content discoverable re-publish records before they expire.

The addr object

FieldPresenceMeaning
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; there is no separate protocol field. Schemes may be introduced without a registry, and clients must skip URLs whose schemes they do not support. FISL (fisl) specifies retrieval.
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). string.
Unknown fields survive. Records and addrs may carry extra fields, for example to signal an endpoint capability the scheme alone cannot express. Indexers must preserve what they do not recognise; clients must ignore it.

The record key is the CID

This is the design decision everything else follows from. One repository holds at most one live record per CID, which fixes the meaning of every repository operation: a put creates or replaces the announcement, a delete retracts it. There is no separate update or revocation mechanism, and no way to hold two records that disagree.

A DASL CID is a conformant record key with no escaping: 59 characters of lowercase base32 with a b prefix, inside the permitted character set, far under the 512-character limit, and free of the case-sensitivity hazard record keys otherwise carry. A BDASL CID has the same string form — it changes only the hash type byte.

Publishers pay for this twice. To add or remove one addr you must read the record, edit addrs, and put it back — use swapRecord to make the put conditional and retry on conflict. And two independent writers cannot announce the same CID side by side; each rewrite replaces the other's addrs. Treat one CID in one repository as owned by one writer.

Lexicon

dev.fisl.at — lexicon
{
  "lexicon": 1,
  "id": "dev.fisl.at",
  "defs": {
    "main": {
      "type": "record",
      "description": "An announcement that the content identified by a DASL or BDASL CID is retrievable at one or more URLs, until an expiry time.",
      "key": "any",
      "record": {
        "type": "object",
        "required": ["cid", "addrs"],
        "properties": {
          "cid": {
            "type": "string",
            "description": "The DASL or BDASL CID being announced. Must equal the record key.",
            "minLength": 59,
            "maxLength": 59
          },
          "addrs": {
            "type": "array",
            "description": "Locations that serve the content.",
            "items": { "type": "ref", "ref": "#addr" },
            "minLength": 1,
            "maxLength": 32
          },
          "size": {
            "type": "integer",
            "description": "Size of the content in bytes.",
            "minimum": 0
          },
          "expires": {
            "type": "string",
            "format": "datetime",
            "description": "Time after which this record is no longer valid. Ceiling for the expiry of every addr."
          }
        }
      }
    },
    "addr": {
      "type": "object",
      "description": "One location that serves the content.",
      "required": ["url"],
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "description": "Absolute URL for the content. The scheme identifies the retrieval method.",
          "maxLength": 2048
        },
        "expires": {
          "type": "string",
          "format": "datetime",
          "description": "Time after which this addr is no longer valid. Defaults to, and is capped by, the record's expires."
        }
      }
    }
  }
}
The prose governs. The key type is any because Lexicon has no pattern-based key type — none of tid, nsid, or literal: admits a CID. So “the key equals cid” can only be stated in prose and checked by indexers, and format: uri is looser than the normative rules. The validator checks both layers.

Indexing

An indexer builds a lookup table from CIDs to live records:

  • Accept a record from a repository — a firehose or relay subscription for the dev.fisl.at collection, a backfill, or both.
  • Verify its inclusion in a signed commit of the publisher's repository. Discard it if verification fails.
  • Parse cid, accepting the BDASL hash type extension. Discard the record if parsing fails.
  • Discard the record if expires is malformed or past, and any addr whose url does not parse or whose effective expiry is past — but never an addr merely because the scheme is unknown. Discard the record if no addrs remain.
  • Store it keyed by cid with the publisher's DID, which FISL serves as publisher. Replace any record previously stored for the same DID and CID.
  • Delete on a delete event.

A single row per (did, cid) pair is enough — no reconciliation logic. Where two events concern the same pair, the later repository commit wins; commit revisions are TID-format logical clocks that sort lexicographically, so it is a string comparison. An indexer that backfills and follows a firehose at once should record the rev alongside each row, so a late-arriving put cannot resurrect a retracted record.

Indexers store what publishers said, not what is true. They do not fetch the URLs, check the content is there, or interpret schemes. Verification belongs to the client at retrieval time — that is the point of content addressing.

Your PDS is already a discovery endpoint

Because the record key is the CID, getRecord answers “does this DID serve this CID?” in one request, with no indexer anywhere. That is not a workaround; it falls out of the key choice.

One request, no indexer
GET https://{pds}/xrpc/com.atproto.repo.getRecord
  ?repo={did}
  &collection=dev.fisl.at
  &rkey={cid}