mx.api

A minimal-dependency client for the Matrix Client-Server HTTP API, suitable for talking to a Synapse or Conduit homeserver from R. Two Imports: curl and jsonlite. No tidyverse.

Pairs with mx.crypto, which handles Olm + Megolm; mx.api itself does no cryptography.

Install

# CRAN
install.packages("mx.api")

# GitHub (development version)
remotes::install_github("cornball-ai/mx.api")

Quick start: log in, send, sync

library(mx.api)

s <- mx_login(
    server   = "https://matrix.example",
    user     = "alice",
    password = "hunter2"
)

room <- mx_room_join(s, "#general:matrix.example")
mx_send(s, room, "hello from R")

batch <- mx_sync(s, timeout = 0)
str(batch$rooms$join)

mx_logout(s)

Create a private room with invites

room <- mx_room_create(
    s,
    name   = "project sync",
    topic  = "weekly check-in",
    preset = "private_chat",
    invite = c("@bob:matrix.example", "@carol:matrix.example")
)
mx_send(s, room, "kickoff in 5")

mx_room_create returns the new room id as a character string. Presets: "private_chat", "trusted_private_chat", "public_chat".

Sending rich-text (code blocks, bold, etc.)

mx_send’s body is plain text. Matrix clients that show rich text (Element, Cinny, Fractal, etc.) render the optional formatted_body field instead, fall back to body when it is absent. Markdown fences in plain body show as literal backticks; you have to send HTML in formatted_body to get a real code block.

Pass both via the extra argument:

plain <- "Build steps:\n\ndocker compose build\ndocker compose up -d"

html <- paste0(
    "<p>Build steps:</p>",
    "<pre><code>docker compose build\n",
    "docker compose up -d</code></pre>"
)

mx_send(
    s, room, plain,
    extra = list(
        format = "org.matrix.custom.html",
        formatted_body = html
    )
)

The format value "org.matrix.custom.html" is the only one the Matrix spec defines. Supported tags are listed in the Matrix client-server spec, section 11.2.1.7. Code blocks use <pre><code>…</code></pre>; inline code is <code>…</code>.

What’s covered

Area Functions
Session mx_register, mx_login, mx_logout, mx_whoami, mx_session
Rooms mx_rooms, mx_room_create, mx_room_join, mx_room_leave, mx_room_invite, mx_room_members, mx_room_name, mx_room_topic
Messages mx_send, mx_send_event, mx_messages, mx_sync, mx_react, mx_redact, mx_read_receipt, mx_typing
Room state mx_get_state, mx_set_state
Media mx_upload (streaming), mx_download, mx_send_media (+ mx_send_file / mx_send_image / mx_send_audio / mx_send_video), mx_guess_mime, mx_media_config
Profile mx_profile, mx_set_displayname, mx_set_avatar_url
Account data mx_get_account_data, mx_set_account_data
Devices mx_devices, mx_delete_device
E2EE transport mx_keys_upload, mx_keys_query, mx_keys_claim, mx_send_to_device
E2EE signing helper mx_canonical_json

End-to-end cryptography is out of scope; pair with mx.crypto (or another crypto library) to sign and verify the payloads these endpoints carry. Helpful framing:

Canonical JSON

mx_canonical_json() is the byte-stable encoder Matrix’s signing rules require. It is hand-rolled (not a jsonlite wrapper) so the spec-sensitive choices — key ordering by UTF-8 byte sequence, integer range, NaN/Inf/NA rejection, duplicate-key rejection, control-char escaping — are visible and unit-tested rather than hidden in another package’s defaults.

mx_canonical_json(list(b = 2, a = 1))
#> [1] "{\"a\":1,\"b\":2}"

mx_canonical_json(1.5)
#> Error: mx_canonical_json: non-integer number 1.5 disallowed

107 assertions exercise the encoder (see inst/tinytest/test_canonical_json.R).

Status

0.3.0. Relative to 0.2.0 the delta is additive:

See NEWS.md for the full changelog.

CI

GitHub Actions via r-ci; macOS and Ubuntu runners cover every commit + PR.

License

MIT. See LICENSE.