3. Discover the schema
Each frontend serves the upstream schema as SATS-JSON over plain HTTPS on the same port as its WebSocket. These bytes are the ones the relay cached at startup and used to codegen the running mirror — so they always match the data you can subscribe to. Prefer the data explorer if you just want to click through tables in the browser.
-
Build the URL from port + mirror database
https://relay.bitcraftsync.app:<port>/v1/database/<mirror>/schema?version=9 -
GET it
Expect
Content-Type: application/json, roughly ~580 KB for a BitCraft module,Cache-Control: public, max-age=60. -
Use table names from the response
Public tables appear under the schema’s table list. Mirror-only
reducers (
relay_apply_*,_relay_meta) are not in this document — it is the upstream schema. -
Feed codegen
Point
spacetime generate/ your SDK generator at this JSON (or a saved copy) so client bindings match the mirror.
curl
curl -fsSL \
"https://relay.bitcraftsync.app:3014/v1/database/relay-mirror-bc14/schema?version=9" \
-o schema-bc14.json
# Quick peek at public table names (jq):
jq -r '.. | objects | select(has("name") and has("columns"))? | .name' \
schema-bc14.json | sort -u | head
Python
import json
import urllib.request
URL = (
"https://relay.bitcraftsync.app:3014"
"/v1/database/relay-mirror-bc14/schema?version=9"
)
with urllib.request.urlopen(URL, timeout=60) as resp:
raw = resp.read()
print(resp.status, resp.headers.get("Content-Type"), f"{len(raw)} bytes")
schema = json.loads(raw)
# Shape is RawModuleDef v9 SATS-JSON. Walk tables for names:
tables = []
def walk(obj):
if isinstance(obj, dict):
if "name" in obj and "columns" in obj:
tables.append(obj["name"])
for v in obj.values():
walk(v)
elif isinstance(obj, list):
for v in obj:
walk(v)
walk(schema)
print(f"{len(tables)} tables, e.g. {tables[:5]}")
Rust
use anyhow::{bail, Result};
#[tokio::main]
async fn main() -> Result<()> {
let url = concat!(
"https://relay.bitcraftsync.app:3014",
"/v1/database/relay-mirror-bc14/schema?version=9",
);
let bytes = reqwest::get(url).await?.error_for_status()?.bytes().await?;
println!("downloaded {} bytes", bytes.len());
// Prefer the same parser the relay uses (SATS-JSON → module def)
// if you depend on relay-protocol; otherwise serde_json::Value is fine
// for listing table names during exploration.
let v: serde_json::Value = serde_json::from_slice(&bytes)?;
if !v.is_object() {
bail!("expected JSON object");
}
println!("schema JSON root keys: {:?}",
v.as_object().unwrap().keys().collect::<Vec<_>>());
Ok(())
}
Generate client bindings
Once you have the schema (or can reach the module), generate SDK bindings the usual SpacetimeDB way. Against a reachable module:
# Example — adjust flags to your spacetime CLI version:
spacetime generate --lang rust \
--out-dir ./module_bindings \
--module-name relay-mirror-bc14 \
-s https://relay.bitcraftsync.app:3014
spacetime generate --lang python \
--out-dir ./module_bindings \
--module-name relay-mirror-bc14 \
-s https://relay.bitcraftsync.app:3014
If generate expects a file, pass the downloaded
schema-bc14.json. Table SQL names in subscribe strings
must match the schema (e.g. claim_state, not the Rust
struct name).