Conventions
How to talk to the DFOS API today: the behavior shared across every resource — pagination, identifiers, and errors. Read this once; the resource pages assume it. For what's guaranteed to stay stable versus what can change over time, see the compatibility contract.
Pagination
The list endpoints (GET /v1/spaces, GET /v1/spaces/{space}/pages,
GET /v1/spaces/{space}/posts, GET /v1/spaces/{space}/topics,
GET /v1/users) use cursor pagination:
limitsets the page size. The default is 20; the minimum is 1 and the maximum is 100.- Pass the response's
nextCursorasafterto fetch the next page. - Pass the response's
previousCursorasbeforeto fetch the previous page. - Do not send
afterandbeforetogether. - A
nullcursor means there is no page in that direction.
When a listing accepts filters or a sort order, hold them constant for the entire pagination walk. Cursors do not encode those inputs; changing them mid-walk can skip or repeat items.
curl 'https://api.dfos.com/v1/spaces?limit=20&after=eyJpZCI6InNwYWNlXzdneDRrMm05In0'
totalCount is the total number of matching items when the endpoint computes
it. It is optional — it may be null or absent entirely, so treat it as a
hint and never require its presence.
Cursors are opaque. Store and return them unchanged; never parse, decode, or construct one. Their internal encoding carries no stability guarantee and can change (see the compatibility contract).
Identifiers
Every resource is addressable by a stable identifier and, where it exists, by a mutable alias:
| Resource | Stable (store these) | Mutable alias (display / links only) |
|---|---|---|
| Space | entity id (space_…), protocol DID | subdomain (domain) |
| User | entity id (identity_…), protocol DID | handle (username) |
| Post | entity id (post_…) | slug |
| Page | id (page_…) | slug |
The stable identifiers point at the same entity for its lifetime. A mutable
alias can change or be released, and a released alias can later resolve to a
different entity — so never key your own storage on a subdomain, handle, or
slug. Legacy internal identifiers are never accepted and return 404 Not Found.
DIDs on the wire
Every did in a response is a protocol DID — did:dfos: followed by 31
chain-derived characters (40 characters total). It is the only DID form this
API speaks. Treat DIDs as opaque, stable identifiers; resolve them against the
relay from GET /v1/protocol.
Errors and not-found
Errors are machine-readable — read the response body's error fields rather than
relying only on the HTTP status. 429 and 503 are covered under
Rate limits and caching.
A 404 Not Found is deliberately ambiguous: a private, missing, or
unknown-identifier resource all return the same 404, so an anonymous
caller can never distinguish "you can't see this" from "this doesn't exist."
The full model is in Content visibility.
Stability
New fields, new enum values, and new endpoints can arrive without a version bump; existing fields and behavior are preserved once the API reaches general availability. Write clients that ignore unknown fields and tolerate unknown enum values — the full rules are in the compatibility contract.