Skip to content

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.

index.js
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.