Threa Developers

    The message content format.

    Message bodies are markdown. On top of standard formatting, a few custom link schemes carry what plain markdown has no syntax for: a mention's identity, a file, a channel. They survive a round trip unchanged.

    The content field

    You send a message body in the content field as a markdown string. The API parses it, resolves any references, and stores a structured document. Reads return the canonical markdown in the same content field.

    Send a message
    curl -X POST /api/v1/workspaces//streams/STREAM_ID/messages \
      -H "Authorization: Bearer " \
      -H "Content-Type: application/json" \
      -d '{ "content": "Deploy is green. Thanks @pierre. Details in #releases." }'

    What you write and what you read back are not always byte-identical. You can write a mention as a bare @pierre; the API resolves it and stores the actor it points at, so the read-back form is the resolved [@pierre](user:usr_…). The text a person sees is the same either way.

    Standard markdown

    Common GitHub-flavored markdown renders as you'd expect. Supported:

    ElementSyntax
    Bold, italic, strikethrough**bold**, *italic*, ~~struck~~
    Inline code, code blocks`code`, triple-backtick fences with a language
    Headings# H1 through ###### H6
    Lists- item, 1. item
    TablesGFM pipe tables
    Blockquotes, rules> quote, ---
    Links[text](https://…)
    Reserved schemes A link whose target starts with one of the schemes below (for example user: or attachment:) is a typed reference, not a plain link; Threa renders it as a chip or card. Use https://, mailto:, or a relative path for ordinary links.

    Math

    TeX between math delimiters renders as an equation. Both the dollar and the backslash form work, so a formula pasted out of a paper or a model's answer arrives as written.

    PlacementSyntax
    Inline, inside a sentence$e^{i\pi}+1=0$, \(e^{i\pi}+1=0\)
    Display, centered on its own line$$\frac{9}{31}$$, \[\frac{9}{31}\]
    You write
    Sample share is \( \frac{9}{31} \approx 29\% \), so
    
    \[ \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi} \]
    It renders as

    Sample share is 931≈29%\frac{9}{31} \approx 29\%, so

    ∫−∞∞e−x2 dx=π\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}

    What sits between the delimiters is TeX, not markdown: \{, \\, * and _ keep their TeX meaning and reach the renderer untouched. Math is content rather than a typed reference, so it has no scheme and no resolved form; you read back the string you sent.

    Dollar amounts stay prose A single $ opens math only when it hugs its content and the closing $ isn't followed by a digit, so costs $5 and $10 total, $50-$60, $1,000 and $PATH are left alone. The price is $5, so $p = 5$. renders the second pair and only that. A $ inside a code span, a fence, or a URL is never a delimiter.

    Mentions and channels

    Write a mention as a bare @slug and a channel as #slug. The API resolves each one against the workspace and stores a reference to the actor or stream, not the slug. Slugs can change; the stored reference doesn't.

    You write
    Ping @pierre and @ariadne about #releases.
    You read back
    Ping [@pierre](user:usr_8x2…) and [@ariadne](persona:persona_a1…) about [#releases](channel:stream_4f…).

    The label inside the brackets is a display name; the link target is the stable id. An actor is a workspace member (user:), an agent like Ariadne (persona:), or a bot (bot:). @here and @channel are broadcasts and resolve to broadcast:here / broadcast:channel.

    If you already know the id, you can write the resolved form directly; it round-trips unchanged. A bare slug that resolves to nothing is left as typed.

    Attachments

    Upload a file first, then reference the returned id in a message. The scheme is attachment:<id>; the link label is the filename shown in the message.

    Upload, then attach
    # 1. upload, returns { "id": "attach_…" }
    curl -X POST /api/v1/workspaces//attachments \
      -H "Authorization: Bearer " \
      -F file=@./report.pdf
    
    # 2. reference that id in a message
    curl -X POST /api/v1/workspaces//streams/STREAM_ID/messages \
      -H "Authorization: Bearer " \
      -H "Content-Type: application/json" \
      -d '{ "content": "Q3 numbers: [report.pdf](attachment:attach_…)" }'

    Emoji

    Write emoji as :shortcode: (for example :tada:). Known shortcodes resolve to the character; an unknown one stays as text. Raw unicode emoji you paste in are folded to their shortcode on the way in, so the stored form is consistent.

    Link scheme reference

    Every typed reference rides on standard [label](scheme:payload) link syntax, so a client that doesn't understand a scheme still shows the label as readable text. The ones you'll write are the first group; the rest you'll mostly encounter on reads.

    SchemeReferenceExample
    user:workspace member mention[@pierre](user:usr_…)
    persona:agent mention (e.g. Ariadne)[@ariadne](persona:persona_…)
    bot:bot mention[@deploybot](bot:bot_…)
    broadcast:@here / @channel[@here](broadcast:here)
    channel:channel link[#releases](channel:stream_…)
    attachment:uploaded file[report.pdf](attachment:attach_…)
    memo:memory card[Auth rewrite](memo:memo_…)
    quote:quoted reply (read)[Alice](quote:stream_…/msg_…/usr_…/user)
    shared-message:cross-stream share (read)[Alice](shared-message:stream_…/msg_…)

    Why the schemes exist

    Threa stores each message as a structured document, not as a string. The markdown you send and read is a projection of that document. Plain markdown has no way to say "this is a mention of member usr_8x2," only "this is the text @pierre." The schemes carry the missing piece: a markdown link with a stable id, so the projection round-trips through the structured form without losing what a reference points at.

    For an integration: send plain, readable markdown, and read the resolved form back. If you only need the text, strip the link syntax to its label; a mention reduces to @pierre, a file to its filename.