Farfield is the content backend for iammatthias.com — it replaces the
atproto PDS as the site's data source. Three independent HTTP services, all
read-only for the website (writes need a bearer token the site does not have):
| Service | Base URL | Holds |
|---|---|---|
| content | https://content.farfield.systems |
posts, open-source, recipes, art, melange, media, series |
| feed | https://feed.farfield.systems |
feed (short posts) |
| blobs | https://blobs.farfield.systems |
image bytes + metadata |
All responses are JSON unless noted. No auth, no API key for reads.
Both services run the same engine; only their collections differ.
{ "service", "ok": true, "cursor": <int>, "collections": [...] } — cursor
is the current global sequence number.
Lists every record in a collection.
{ "records": [ { "collection", "rkey", "cid", "value": {...} }, ... ],
"cursor": 624 }Sends an ETag for the whole list — send it back as If-None-Match to get a
304.
Incremental sync: only records changed after sequence {seq}.
{ "records": [...], "deletions": [ {"collection","rkey"}, ... ], "cursor": 700 }Poll with the previous cursor to pull just what changed.
One record: { "collection", "rkey", "cid", "value": {...} }. The ETag is
the record's cid (a content hash) — stable until the record changes, so it
is safe as a cache key and for If-None-Match.
404 if absent.
The lexicon-lite schemas — field names, types, which are required.
value is the record body. Field types: datetime is an RFC3339 UTC string.
All five share the entry shape. rkey == slug.
| field | type | notes |
|---|---|---|
title |
string | |
slug |
string | <timestamp>-<words>, also the rkey |
published |
boolean | drafts are false — filter these out |
created |
datetime | |
updated |
datetime | |
tags |
string[] | |
excerpt |
string | optional |
body |
string | markdown — see "Body URIs" below |
One record per image. rkey == the blob cid.
cid, mime, width (int), height (int), blurhash, dominantColor
(hex, e.g. #3a2f1c), alt (optional), created.
Use this to render an image with a blur-up placeholder and correct aspect-ratio box before the bytes load.
A gallery — an ordered group of media. rkey is a content hash of its refs.
title (optional — e.g. "Generation 1"), description (optional),
refs (string[] of media cids, in display order), created.
Short posts (currently empty). body (markdown), created, link
(optional), tags (optional). Provisional shape.
Markdown bodies contain two custom URI schemes the renderer must resolve:
-
— a single image. Render<cid>ashttps://blobs.farfield.systems/blobs/<cid>. For dimensions / blurhash / dominant color, read themediarecord:GET /records/media/<cid>. -
— a gallery. FetchGET /records/series/<rkey>; rendervalue.refs(media cids, in order) as a gallery — each ref is ablob://image, look up itsmediarecord the same way.
No ipfs:// URIs remain — every image was migrated onto blobs.
GET /blobs/{cid}— the raw image bytes. SetsContent-TypeandCache-Control: public, max-age=31536000, immutable— content-addressed, so the bytes for a cid never change; cache them forever.GET /blobs/{cid}/meta—{ cid, size, mime, width, height, blurhash, dominantColor }.GET /blobs—{ "blobs": [cid, ...] }.
- Replace PDS record reads with
GET /records/{collection}/GET /records/{collection}/{rkey}againstcontent.farfield.systems(andfeed.farfield.systemsfor the feed). - Filter content entries by
published === true. - Render markdown bodies, resolving
blob://andseries://as above. - Drop any IPFS gateway logic — images are served by the blob service.
- Use the record
cid(theETag) as the cache key; optionally poll?since={cursor}for incremental rebuilds.