Skip to content

The Antiphony lexicons

The lexicons are the heart of Antiphony. They are the AT Protocol record definitions under the dev.antiphony.* namespace that describe everything the system stores — portably, content-addressably, and legibly to existing AT Protocol tooling. The REST API, the Zod schemas in packages/shared, and every app built on the core all derive from these.

If you read one thing before building on Antiphony, read this.

The source of truth is the JSON in lexicons/dev/antiphony/. This page is the guided tour.

Antiphony’s lexicons mirror the Bluesky (app.bsky.*) shapes wherever one exists, and contribute new ones only where the protocol has a gap. dev.antiphony.audio.post is structured like app.bsky.feed.post; dev.antiphony.embed.audio is modeled on app.bsky.embed.video. The payoff: a record an AT Protocol client already understands, differing only where audio call-and-response genuinely needs something new.

Two such gaps are filled here:

  • Audio embeds. atproto has image, video, external, and record embeds — but no audio. dev.antiphony.embed.audio is the protocol’s audio attachment.
  • Timed transcripts. A machine transcript modeled as platform enrichment, not a field the author writes.

dev.antiphony.audio.post — the one canonical content record

Section titled “dev.antiphony.audio.post — the one canonical content record”

A single record type carries both halves of call-and-response:

  • A post without a reply is a prompt — the root of a thread.
  • A post with a reply is a reply.

reply presence is the discriminator — not title, which is an optional prompt-only headline. The audio rides in embed; the typed text is the author’s question or caption (max 300 graphemes / 3000 bytes, bsky-semantic), never the transcript.

FieldTypeNotes
textstringUser-authored text. May be empty for pure-audio posts. Required.
titlestring?Optional headline. A prompt feature; not the discriminator.
embedunion?dev.antiphony.embed.audio, …embed.recordWithAudio, app.bsky.embed.record, or …embed.external.
replyref?Present iff this is a reply. { root, parent }, each a com.atproto.repo.strongRef.
langsstring[]?BCP-47 language tags (max 3).
labelsunion?Author-applied com.atproto.label.defs#selfLabels (content warnings).
createdAtdatetimeISO 8601. Required.

Threading is content-addressed. A reply’s reply.root points at the prompt at the top of the thread; reply.parent points at the post being directly answered (the prompt, or another reply). Both are StrongRefs ({ uri, cid }) — portable pointers that replace the legacy flat promptId.

dev.antiphony.embed.audio — the audio attachment

Section titled “dev.antiphony.embed.audio — the audio attachment”

Antiphony’s contribution to the AT Protocol embed family. The record form holds the stored, render-time-independent bytes and metadata; the view form is what a read returns.

Record (#main):

FieldTypeNotes
audioblobaudio/*, up to 100 MB, content-addressed by CID. Required.
durationMsinteger?Duration in milliseconds (the platform-wide unit).
altstring?User-authored short description (the audio analogue of image alt text). Not the transcript.
waveforminteger[]?Pre-computed visual peaks, normalized 0–100, for instant rendering.

View (#view) — what hydration returns:

FieldTypeNotes
urluriResolved, playable URL. In the centralized deployment this is a short-lived signed Storage URL — not the raw blob. Required.
durationMs, alt, waveformCopied from the record embed.
transcriptref?The timed transcript, lifted from the enrichment record at read time. Absent until transcription completes.

The split is the important part: the record stores universal facts; the view carries the resolved playback URL and the lifted transcript. The transcript is never stored on the post.

dev.antiphony.audio.transcript — platform enrichment

Section titled “dev.antiphony.audio.transcript — platform enrichment”

A machine-generated, timed transcript of a post’s audio. It is platform-owned enrichment (like denoise or waveform generation), stored as its own record that references the post by StrongRef — the same pattern as likes and labels. It is lifted into dev.antiphony.embed.audio#view.transcript at read time.

FieldTypeNotes
subjectstrongRefThe post whose audio this transcribes. Required.
transcriptrefA #timedTranscript (segments + optional text rollup). Required.
langstring?BCP-47 language tag of the transcript.
modelstring?Identifier of the model/provider (provenance).
createdAtdatetimeRequired.

A #timedTranscript is an array of #segments — each { startMs, endMs, text } — plus an optional concatenated text rollup for consumers that don’t need timing (the audio analogue of WebVTT captions).

dev.antiphony.embed.recordWithAudio — quote + audio

Section titled “dev.antiphony.embed.recordWithAudio — quote + audio”

The audio analogue of app.bsky.embed.recordWithMedia: a post that both quotes another record (app.bsky.embed.record) and carries its own dev.antiphony.embed.audio.

dev.antiphony.actor.profile — the actor profile

Section titled “dev.antiphony.actor.profile — the actor profile”

One record per actor at the well-known self rkey, mirroring app.bsky.actor.profile. Carries a public handle (distinct from the AT Protocol identity handle), an optional usageIntent (e.g. Podcaster, Listener), and an optional rssFeed URL.

dev.antiphony.audio.post (prompt: no reply)
▲ reply.root / reply.parent (StrongRef)
dev.antiphony.audio.post (reply: has reply)
│ embed
dev.antiphony.embed.audio ──(#view.transcript lifted at read time)──▶ dev.antiphony.audio.transcript

A prompt and its replies are all audio.post records, threaded by StrongRef. Each carries an embed.audio. The transcript lives in its own record and is folded into the embed’s view only when it exists.