Aero developer docs
Full example: a Welcome Aero
A complete client that greets every new member in a chosen Space and handles a /welcome command, using the raw gateway and runtime API.
The whole Aero
Set AERO_TOKEN and WELCOME_SPACE_ID, then run it with Node.
import { io } from 'socket.io-client';
const TOKEN = process.env.AERO_TOKEN;
const BASE = 'https://web.ballastra.app';
const SPACE = process.env.WELCOME_SPACE_ID;
async function api(path, { method = 'GET', body } = {}) {
const res = await fetch(`${BASE}/api${path}`, {
method,
headers: { 'Authorization': `Aero ${TOKEN}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw Object.assign(new Error(data.error), { code: data.code });
return data;
}
const me = await api('/aero/rt/me');
console.log(`I am "${me.aero.name}", installed in ${me.installations.length} Nexus(es).`);
const socket = io(BASE, { transports: ['websocket'], auth: { aeroToken: TOKEN } });
socket.on('connect', () => socket.emit('user:register', { userId: me.aero.user_id }));
const seen = new Set();
socket.on('aero:event', async (ev) => {
if (seen.has(ev.id)) return; seen.add(ev.id); // idempotent
if (ev.type === 'member.joined') {
const m = ev.data.member;
if (!m || m.is_aero) return;
await api(`/aero/rt/nexus/${ev.nexus_id}/spaces/${SPACE}/messages`, {
method: 'POST', body: { text: `Welcome, ${m.name}! 👋` },
});
}
});
socket.on('aero:interaction', async (i) => {
if (i.type !== 'command' || i.command !== 'welcome') return;
const who = i.options?.name || i.invoker?.name || 'friend';
await api(`/aero/rt/nexus/${i.nexus_id}/spaces/${i.space_id}/messages`, {
method: 'POST', body: { text: `Welcome, ${who}! 👋` },
});
});That's the whole contract. Handle the events you subscribed to, act through the runtime API, and dedupe on the event and interaction id, since delivery is at-least-once.