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.
{
"$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"
}
| Field | Presence | Meaning |
|---|---|---|
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
| 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; 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. |
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.
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
{
"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."
}
}
}
}
}
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.atcollection, 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
expiresis malformed or past, and any addr whoseurldoes 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
cidwith the publisher's DID, which FISL serves aspublisher. 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.
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.
GET https://{pds}/xrpc/com.atproto.repo.getRecord
?repo={did}
&collection=dev.fisl.at
&rkey={cid}