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:
https://web.ballastra.app/apiA small helper used throughout these docs:
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
/api/aero/rt/meidentity + installs/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messagesbody: { text, embeds?, components? }/api/aero/rt/nexus/:nexusId/modalsopen a form (needs an interaction)/api/aero/rt/nexus/:nexusId/spacescreate a space · Manage spaces/api/aero/rt/nexus/:nexusId/members/:userId/rolesassign a role · Manage roles/api/aero/rt/nexus/:nexusId/members/:userId/timeoutmute a member · Moderate chat/api/aero/rt/nexus/:nexusId/members/:userIdkick · Remove members/api/aero/rt/nexus/:nexusId/members/:userId/banban · OUT members/api/aero/rt/nexus/:nexusId/members/:userId/warnwarn · Moderate chat/api/aero/rt/nexus/:nexusId/spaces/:spaceId/purgebulk delete · Manage messages/api/aero/rt/nexus/:nexusId/spaces/:spaceId/slowmodeslowmode · Manage spaces/api/aero/rt/nexus/:nexusId/spaces/:spaceId/lockdownlock/unlock · Manage spaces/api/aero/rt/nexus/:nexusId/rolescreate a role · Manage roles/api/aero/rt/nexus/:nexusId/categoriescreate a category · Manage categories/api/aero/rt/nexus/:nexusId/invitescreate an invite · Create invitesReading
/api/aero/rt/nexus/:nexusId/memberslist members · View members/api/aero/rt/nexus/:nexusId/members/:userIdone member/api/aero/rt/nexus/:nexusId/roleslist roles/api/aero/rt/nexus/:nexusId/spaceslist spaces in scope/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messageshistory · Read messages/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactionswho reacted/api/aero/rt/nexus/:nexusId/members/:userId/warningswarning history · Moderate chatMessages
/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageIdedit your own message/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageIddelete · Manage messages/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/pinpin or unpin/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactionsadd a reaction/api/aero/rt/nexus/:nexusId/spaces/:spaceId/messages/:messageId/reactions/:emojiremove oneSpaces, roles and moderation
/api/aero/rt/nexus/:nexusId/spaces/:spaceIdrename or re-topic · Manage spaces/api/aero/rt/nexus/:nexusId/spaces/:spaceIddelete a space you created/api/aero/rt/nexus/:nexusId/spaces/:spaceId/membersadd someone to a private space/api/aero/rt/nexus/:nexusId/spaces/:spaceId/members/:userIdremove them again/api/aero/rt/nexus/:nexusId/categories/:categoryId/visibilityshow or hide a category/api/aero/rt/nexus/:nexusId/members/:userId/roles/:roleIdtake a role away/api/aero/rt/nexus/:nexusId/bans/:userIdlift an OUT/api/aero/rt/nexus/:nexusId/members/:userId/warningsclear warningsEvery 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:
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.