Skip to content

Aero developer docs

Commands and interactions

Register slash commands, handle prefix commands, and answer every interaction the way members expect.

Slash commands

Register a slash command in the portal with a name, a description, and an optional required permission and cooldown. When a member runs /yourcommand in a Space, Ballastra runs the full permission chain and sends you an aero:interaction:

Interaction payload
socket.on('aero:interaction', async (i) => {
  if (i.type !== 'command') return;
  // i = {
  //   interaction_id,   // dedupe on this
  //   type: 'command',
  //   command,          // the command name, e.g. 'welcome'
  //   options,          // { ...args } (object; may be empty)
  //   nexus_id, space_id, installation_id,
  //   invoker,          // { id, name } — who ran it
  // }
  if (i.command === 'welcome') {
    // Respond by acting through the runtime API:
    await api(`/aero/rt/nexus/${i.nexus_id}/spaces/${i.space_id}/messages`, {
      method: 'POST',
      body: { text: `Hi ${i.invoker.name}!` },
    });
  }
});

You respond by taking an action. There is no separate reply channel: posting a message is the reply.

Prefix commands

Prefer classic prefix commands like !ban? Subscribe to message.created, grant the Aero Read messages, and the SDK does the parsing for you:

Prefix commands (SDK)
aero.prefix('!');
aero.prefixCommand('ping', (ctx) => ctx.reply('pong'));
aero.prefixCommand('ban', async (ctx) => {
  // ctx.args = ['@user', 'reason', ...]
  await ctx.reply(`Banning ${ctx.args[0]}`);
});

Answering an interaction

A button press or a slash command gives you an interaction_id. There are three ways to answer it, and the one you pick is what the member sees:

Three response modes
// 1. Ephemeral — only the presser sees it, nothing is stored.
await api(`/aero/rt/nexus/${nexusId}/interactions/respond`, {
  method: 'POST',
  body: { interaction_id: i.interaction_id, mode: 'ephemeral', text: 'Only you can read this.' },
});

// 2. Update — rewrite the message that carried the button, for everyone.
//    Use this to disable a button after it is pressed.
await api(`/aero/rt/nexus/${nexusId}/interactions/respond`, {
  method: 'POST',
  body: {
    interaction_id: i.interaction_id,
    mode: 'update',
    text: 'Signed up. See you Friday.',
    components: [],
  },
});

// 3. A normal message, if the answer belongs in the space for everyone.
await api(`/aero/rt/nexus/${nexusId}/spaces/${i.space_id}/messages`, {
  method: 'POST',
  body: { text: `${i.member.name} signed up.` },
});

Answer within a few seconds. An interaction the member pressed and never saw answered reads as a broken Aero, so reply first and do the slow work after.