Skip to content

Aero developer docs

Runtime API

Everything an Aero does — posting, moderating, reading — is a plain HTTPS call to the runtime API, checked against the permissions its install was granted.

Base URL and helper

Calls are authenticated with your Aero token. Base URL:

Base URL
https://web.ballastra.app/api

A small helper used throughout these docs:

api() helper
const BASE = 'https://web.ballastra.app';
async function api(path, { method = 'GET', body } = {}) {
  const res = await fetch(`${BASE}/api${path}`, {
    method,
    headers: {
      'Authorization': `Aero ${process.env.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;
}

Actions

GET/api/aero/rt/meidentity + installs
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messagesbody: { text, embeds?, components? }
POST/api/aero/rt/nexus/:nexusId/modalsopen a form (needs an interaction)
POST/api/aero/rt/nexus/:nexusId/spacescreate a space · Manage spaces
POST/api/aero/rt/nexus/:nexusId/members/:userId/rolesassign a role · Manage roles
POST/api/aero/rt/nexus/:nexusId/members/:userId/timeoutmute a member · Moderate chat
DELETE/api/aero/rt/nexus/:nexusId/members/:userIdkick · Remove members
POST/api/aero/rt/nexus/:nexusId/members/:userId/banban · OUT members
POST/api/aero/rt/nexus/:nexusId/members/:userId/warnwarn · Moderate chat
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/purgebulk delete · Manage messages
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/slowmodeslowmode · Manage spaces
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/lockdownlock/unlock · Manage spaces
POST/api/aero/rt/nexus/:nexusId/rolescreate a role · Manage roles
POST/api/aero/rt/nexus/:nexusId/categoriescreate a category · Manage categories
POST/api/aero/rt/nexus/:nexusId/invitescreate an invite · Create invites

Reading

GET/api/aero/rt/nexus/:nexusId/memberslist members · View members
GET/api/aero/rt/nexus/:nexusId/members/:userIdone member
GET/api/aero/rt/nexus/:nexusId/roleslist roles
GET/api/aero/rt/nexus/:nexusId/spaceslist spaces in scope
GET/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messageshistory · Read messages
GET/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactionswho reacted
GET/api/aero/rt/nexus/:nexusId/members/:userId/warningswarning history · Moderate chat

Messages

PATCH/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageIdedit your own message
DELETE/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageIddelete · Manage messages
PUT/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/pinpin or unpin
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactionsadd a reaction
DELETE/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactions/:emojiremove one

Spaces, roles and moderation

PATCH/api/aero/rt/nexus/:nexusId/spaces/:spaceIdrename or re-topic · Manage spaces
DELETE/api/aero/rt/nexus/:nexusId/spaces/:spaceIddelete a space you created
POST/api/aero/rt/nexus/:nexusId/spaces/:spaceId/membersadd someone to a private space
DELETE/api/aero/rt/nexus/:nexusId/spaces/:spaceId/members/:userIdremove them again
PUT/api/aero/rt/nexus/:nexusId/categories/:categoryId/visibilityshow or hide a category
DELETE/api/aero/rt/nexus/:nexusId/members/:userId/roles/:roleIdtake a role away
DELETE/api/aero/rt/nexus/:nexusId/bans/:userIdlift an OUT
DELETE/api/aero/rt/nexus/:nexusId/members/:userId/warningsclear warnings

Every action is gated: it must target a Nexus you're installed in and a Space in scope, and it is checked against your granted role. If you lack the grant you get a 403 with a code (see error codes), never a silent failure.

Direct messages

An Aero may DM a member of a Nexus it is installed in. There is no opt-in: members are reachable by default and opt out per Aero from their own settings. Check before you send if you want to fail quietly rather than catch a 403:

DM a member
const { allowed } = await api(`/aero/rt/nexus/${nexusId}/dm/${userId}/can`);
if (allowed) {
  await api(`/aero/rt/nexus/${nexusId}/dm`, {
    method: 'POST',
    body: {
      user_id: userId,
      text: 'Your application was accepted.',
      embeds: [{ title: 'Next steps', description: 'Reply here if you have questions.' }],
    },
  });
}

Every refusal returns the same error whatever the cause: opted out, not a member, or no shared Nexus. That is deliberate, so a DM attempt cannot be used to find out who exists or who blocked you.