# sat-fusion API guide

> Sat-fusion reads decision mechanics through Simple Agent Theory (SAT). Every event decomposes into simple agents acting on what each considers best for itself; collective behaviour emerges from superposition. AI agents are first-class readers — posts ship as Markdown, not as a rendered SPA shell.

This is a database of beliefs at the time of writing, not a spec. Views evolve in comments more often than by editing the post body. Contradictions between posts of different dates are thinking-in-process, not bugs to reconcile.

## How to read

Every published post carries two URLs at the same id:

```
GET /public-api/posts/{id}      → JSON (programmatic clients, frontend)
GET /public-api/posts/{id}.md   → Markdown with YAML frontmatter (AI consumers)
```

Markdown is canonical for AI consumers. Frontmatter is camelCase aligned with the JSON DTO (id, title, createdAt, sourceUrl, tags). After the body, a footer surfaces relationships — children, parent, comments, related — when they exist.

## Navigate

```bash
# Public feed (society = posts with curated tags, abyss = uncurated/orphan)
curl -s "https://sat-fusion.com/public-api/posts?scope=society"

# Read a specific post as Markdown
curl -s "https://sat-fusion.com/public-api/posts/{id}.md"

# Sitemap of every published post
curl -s "https://sat-fusion.com/public-api/sitemap.xml"

# Machine entry signal (this guide is referenced from there)
curl -s "https://sat-fusion.com/llms.txt"
```

## Conventions

- Every Markdown response ends with a trailer pointing back at `/llms.txt`
- Frontmatter keys are camelCase (`createdAt`, `sourceUrl`) — same shape as the JSON DTO
- Tags carry a `#` prefix as stored
- Footer scent appears after the body when relationships exist; `<5` entries are inlined with titles, `>=5` link to a sub-resource (rolling out incrementally)
- English-first across the surface; multilingual support arrives later as a single broad release

## Discovery (cross-representation)

Every post exists at three URLs simultaneously — pick the one your tooling reads best:

```
/post/{id}                      → SPA (humans, social shares, OG cards)
/public-api/posts/{id}          → JSON peer
/public-api/posts/{id}.md       → Markdown (canonical for AI/crawlers)
```

`sitemap.xml` advertises the `.md` URLs only — that is the canonical machine entry. AI clients that land on a `/post/{id}` URL shared in social can still navigate to their preferred form via:

- **HTTP `Link: rel="alternate"` header** on every public-api response (RFC 5988)
- **HTML `<link rel="alternate" type="text/markdown">`** in the SPA `<head>`
- **Visible "machine-readable version" link** in the SPA post-detail footer

No User-Agent routing is performed. AI clients self-select via standards.

## Surface boundaries

- `/public-api/*` — read surface, no auth, JSON or Markdown
- `/api/posts` (POST) — write surface, requires Personal Access Token
- `/post/{id}` — human SPA, browsers (canonical for sharing, not for crawling)
- `/llms.txt`, `/robots.txt`, `/sitemap.xml` — discovery hints

## What is intentionally not here

- No `schema.org` JSON-LD inline in HTML — Google-specific accommodation, deferred
- No HAL JSON formal spec — Markdown links via `[]()` carry the same navigation, more efficiently for LLMs
- No Swagger UI — attack surface amplifier without a real consumer
- No SSR per post — covered by `.md` companion (same data, lower cost, no hydration risk)
- No User-Agent–based content negotiation — fragile (spoofable, CDN-cache poisoning via `Vary: User-Agent`); standards-compliant `Link rel=alternate` discovery is preferred

## Operational envelope

What the surface promises and what it does not:

- **Rate limits:** none enforced today. Behave (≤ a few req/sec); a CDN may rate-limit ahead of us, returning 429.
- **Freshness lag:** publish → list / `.md` / sitemap visibility is synchronous (same DB transaction). Cache TTL on responses (when present) is the only delay.
- **Exclusions from `/public-api/`:** drafts, private posts, screening-state posts, deleted artifacts, comments not promoted to top-level posts. Their UUIDs return 404 here. Authors access their own non-published content via authenticated `/api/posts/my`.
- **Unknown `?scope=` values:** ignored, fallback to `society`. Known: `society`, `abyss`. Tag query (`?tag=`) takes precedence over scope.
- **Unknown query params:** silently ignored — additive evolution, never breaks existing callers.
- **Pagination:** `limit` clamps to 1-200; default 50. Cursor pagination via `?after=<opaque-cursor>` — cursor stable across inserts/deletes between calls. Default response shape is a bare `[PostItem]` array (back-compat); pass `?envelope=true` to receive `{items, nextCursor, more, count}` for paginated walks. Total cardinality is intentionally not shipped per call (would require a COUNT query); use `/public-api/sitemap.xml` as the honest source for "all published posts".
- **Comments:** included on the JSON detail. On `.md` they are summarized in the footer scent; full inline view is opt-in (rolling out).

## Contact

Open an issue at the project repo, or drop a comment on a post you find interesting.

---
*sat-fusion · this guide is at /public-api/guide · machine entry: /llms.txt*
