Skip to main content

Product catalogue

The set of products you can subscribe to is not hardcoded in these docs — it's served by a single manifest endpoint. Every product is identified by its alias (the example throughout is helios); the manifest is the authoritative list of those aliases and the bundles they're grouped into.

The manifest endpoint

curl https://signals.btcalpha.com.au/v1/manifest

Shape:

{
"schema_version": "1.0",
"products": [
{ "alias": "helios", "name": "Helios", "blurb": "Short blurb describing the product." }
],
"bundles": {
"...": ["helios", "..."]
}
}
FieldTypeMeaning
schema_versionstringManifest schema version.
productsarrayOne entry per product: { alias, name, blurb }. alias is the identifier you key on.
bundlesobjectNamed groupings — each value is a list of alias strings.

Because the manifest is the single source of truth, your integration should read it at runtime rather than maintaining its own copy. New products appear in products with a new alias; your webhook receiver already routes on alias, so nothing in your code changes.

Live, data-driven render

The list below fetches /v1/manifest in your browser and renders whatever the manifest currently returns — no per-product hardcoding on this page:

import React, { useEffect, useState } from "react";

export default function Catalogue() {
const [m, setM] = useState(null);
const [err, setErr] = useState(null);

useEffect(() => {
fetch("https://signals.btcalpha.com.au/v1/manifest")
.then((r) => r.json())
.then(setM)
.catch((e) => setErr(String(e)));
}, []);

if (err) return <p>Could not load the manifest: {err}</p>;
if (!m) return <p>Loading the live catalogue…</p>;

return (
<div>
<h3>Products</h3>
<table>
<thead>
<tr><th>Alias</th><th>Name</th><th>Blurb</th></tr>
</thead>
<tbody>
{m.products.map((p) => (
<tr key={p.alias}>
<td><code>{p.alias}</code></td>
<td>{p.name}</td>
<td>{p.blurb}</td>
</tr>
))}
</tbody>
</table>

<h3>Bundles</h3>
<ul>
{Object.entries(m.bundles).map(([name, aliases]) => (
<li key={name}>
<strong>{name}</strong>: {aliases.map((a) => <code key={a}>{a}</code>).reduce((acc, el) => acc === null ? [el] : [...acc, ", ", el], null)}
</li>
))}
</ul>
</div>
);
}

The same fetch works from any client — pull /v1/manifest on startup to discover available aliases, then subscribe to the ones you want.

import requests
m = requests.get("https://signals.btcalpha.com.au/v1/manifest", timeout=10).json()
for p in m["products"]:
print(p["alias"], "—", p["blurb"])

:::note The alias is the contract Across the entire integration — webhook payloads, the pull API, the manifest — the only product discriminator is the alias. Build against alias and the catalogue can grow without any change on your side. :::