# Introduction

A brief introduction to Superuser

## What is Superuser?

[Superuser](http://superuser.app/) is team chat with agents that use hosted tools. The easiest way to think about Superuser is like, "Discord or Slack with better bots." Our goal is to integrate AI seamlessly into team chat.

### Team chat

* IRC-inspired social chat you've come to know and love
* Workspace / server support via *organizations*
* Public and private channels
* Direct messages, notifications
* Friends, emojis, reactions, /me messages
* File uploads
* Person-to-person chat is **completely free**

### Agents

* Bots that come with AI tooling pre-packaged
* Use a pre-built agent or create your own
* Choose your LLM, define your system prompt
* Add additional context with notes, file uploads and websites
* Extend with **hosted tools** for custom functionality
* We keep your agents online 24/7, no hosting required
* Agents billed by token consumption

### Hosted tools

* APIs powered by JavaScript
* Use pre-built tools or create your own
* They are exposed via traditional HTTP API and MCP bindings
* They execute custom code on your behalf
* Version control, security and authentication built-in
* Can be **private**, **public** or **open source**
* As the name implies, we host these tools for you
* Tools are billed by compute usage
* [View tool registry](https://superuser.app/toolkits)

## What can I use Superuser agents for?

* Analytics and reporting
  * Hook up to your [PostgreSQL](https://superuser.app/org/superuser/toolkits/postgres) instance and execute queries
* Image generation
  * Generate images with [Nano Banana 2](https://superuser.app/org/superuser/toolkits/nano-banana-2) or [GPT image](https://superuser.app/org/superuser/toolkits/gpt-image)
* Research and web queries
  * [Exa search](https://superuser.app/org/superuser/toolkits/exa-search), [Perplexity search](https://superuser.app/org/superuser/toolkits/perplexity-search) or [Wikipedia](https://superuser.app/org/superuser/toolkits/wikipedia)
* Personal use
  * [Retrieve the weather](https://superuser.app/org/superuser/toolkits/open-meteo) or [find a YouTube video](https://superuser.app/org/superuser/toolkits/youtube)

## How does pricing work?

You can view up-to-date pricing at [Superuser / Pricing](https://superuser.app/pricing). Quick summary;

* All **person-to-person chat** is **completely free**
* We charge a monthly subscription fee for access to agents
  * Starting at $20 / mo for 5,000 messages / month
  * Overage is charged per-token to your credit balance
* We charge per-use for hosted tools at a rate of 50 credits per 1,000 GB-s of usage
  * 200ms execution for a hosted tool with 512MB of RAM is 0.005 credits
  * 1,000 function calls at this rate would be 5 credits


# Your workspace

Your personal scratchpad

If you're familiar with ChatGPT, Discord and Slack, the Superuser interface should feel familiar. In this guide we'll cover the web and desktop interface, but the mobile interface is very similar. We'll cover the basics for you to make it easy. Your workspace can be segments into several sections;

* [User menu](#user-menu)
* [Organizations list](#organizations-list)
* [Discover button](#discover-button)
* [Quick navigation](#quick-navigation)
* [Primary actions](#primary-actions)
* [Active chats](#active-chats)
* [Chat window](#chat-window)
* [Chat summary](#chat-summary)

## User menu

<figure><img src="/files/8TODQYck8Ubr7qfnBTkx" alt=""><figcaption></figcaption></figure>

Your user menu is in the **top left** of your workspace. You can access your personal organization view — where you can manage your own private agents, tools and chats — at any time by clicking your avatar in the top left. Once selected you'll also see a **settings cog** on the very right of the user menu. Click this at any time to visit your personal dashboard.

### Personal dashboard

<figure><img src="/files/LVT5Jdsu8nEZVFhQgeIQ" alt=""><figcaption></figcaption></figure>

Your personal dashboard can be used to;

* Profile
  * Edit your profile and about me
  * Change your theme
* Account
  * Change your username
  * Change your password
* Invites
  * Manage personal invitations to chat
* Billing
  * Manage your subscription
  * View compute credit balance and add credits
  * Manage payment methods
* Developer
  * Manage API keychains to use tools with third-party services
  * Manage which Discord servers your agents are connected to
* Reference
  * View documentation
  * View the homepage
* Log out

## Organizations list

<figure><img src="/files/63Y5RwvIpY0PSYmksXAj" alt=""><figcaption></figcaption></figure>

The organizations list is on the left of the screen, immediately under the user menu. When you select an organization from this list, you'll immediately enter the [organization view](/team-chat/organizations-teams). You can also click the **\[+]** button to create an organization.

### Discover button

<figure><img src="/files/RhQFhV9DyMhLtLzONDdF" alt=""><figcaption></figcaption></figure>

The discover button is on the bottom left of your workspace, immediately above the quick navigation menu. Click this to easily discover agents and tools published by Superuser and the community.

## Quick navigation

<figure><img src="/files/iZPrCp4w6f2jhzvRX7w3" alt=""><figcaption></figcaption></figure>

The quick navigation is located in the bottom left of your workspace and has four icon buttons;

* Notifications
  * Quickly view your notifications over the last 30 days and clear all notifications
* Friends
  * [Manage your friends](/team-chat/your-workspace/adding-friends)
* Chats
  * Shortcut to your private chats
* New chat
  * [Chat like ChatGPT, Claude or Gemini](/team-chat/your-workspace/chat-like-chatgpt-claude-or-gemini): use this as a shortcut to start a new conversation in your personal workspace with your favorite agent

## Primary actions

<figure><img src="/files/V2aG4UDvnNmWLmTAXJlf" alt=""><figcaption></figcaption></figure>

Your primary actions for your personal workspace are available just under your user menu, in the leftmost panel after your organizations list. You must have your own workspace selected to view it. If you are not currently subscribed to a personal plan, you'll see an upgrade notice here. Otherwise, there are three main actions;

* New chat
  * [Chat like ChatGPT, Claude or Gemini](/team-chat/your-workspace/chat-like-chatgpt-claude-or-gemini): use this as a shortcut to start a new conversation in your personal workspace with your favorite agent
* Agents
  * View, manage and chat with agents you have added to your personal account
* Toolkits
  * View and manage tools you have published to your personal account

## Active chats

<figure><img src="/files/Lb7SIXwF1x9UTxz4U9TJ" alt=""><figcaption></figcaption></figure>

Your active chats for your personal workspace will show a list of all direct messages, agent chats and group chats you have started that are **not part of an organization**.

* Direct messages will show the **user icon on the left**
* Agent chats and group chats will show **all participant icons on the right**
* You can click on the three dots **\[ . . . ]** to the right of a DM or chat to change the chat settings or archive the chat

## Chat window

<figure><img src="/files/plZIv7bDtqfjXUAgGYiD" alt=""><figcaption></figcaption></figure>

The chat window is the primary communication interface for both other people and agents on Superuser. When you have a direct message, agent chat or group chat selected, the contents will appear here along with a textbox to send messages with.

* At the top of the chat window is the chat title. If you are in an agent chat, you'll see a new chat icon to quickly start a new chat with the same agent. Any agent chat or group chat will also have an invite button to easily add new users. Agent chats can be turned into group chats instantly.
* **Group chats started from DMs will create a new chat**. New members will not be able to see chat history.
* **Group chats started from agent chats will add new members to the existing chat**. New members will be able to see chat history.
* Upload files to chat by pressing the image icon to the left of the chat textbox at the bottom of the screen.

## Chat summary

<figure><img src="/files/d01A4jpEICUY05QVlZDZ" alt=""><figcaption></figcaption></figure>

The chat summary is on the right of the screen. It shows you information about the current chat or direct message.

* For direct messages, it will display the user and information about their friends list
* For agent chats, it will display statistics about agent usage
* For group chats, it will display a list of participants


# Chat like ChatGPT, Claude or Gemini

You do not need to work with a team to make use of Superuser, you can use it just like you would ChatGPT, Claude or Gemini — only you get to use your personal agents that you have customized instead.

To start a new **Agent chat** with an AI agent, just click the **\[ New chat ]** button under your primary actions in your workspace.

<figure><img src="/files/G9aHYxRubBbRDRaQfbEX" alt=""><figcaption></figcaption></figure>

You'll be brought to the new chat interface with your favorite agent already selected. Just type a new message and hit send to start a conversation.

<figure><img src="/files/xwxfhj7F0MJ3KvjPz66A" alt=""><figcaption></figcaption></figure>

That's it! You can now chat back-and-forth with your agent just like you would use ChatGPT, Claude or Gemini.


# Adding friends

While on Superuser, you may encounter people you particularly enjoy chatting with. These could be friends, colleagues at work or anybody, really! You can add a friend at any time by clicking the **\[ Add friend ]** button by their name anywhere you can see their profile.

<figure><img src="/files/TrMaLr9B2TV7T5BLlFWy" alt=""><figcaption></figcaption></figure>

In this example, I've seen a friend in the Members list of a public channel I'm part of. I just clicked on his name, and I can see I'm already friends! If you're not friends yet, you can use this button to add them.

You can also see the same button if you have a direct message or private chat with the person:

<figure><img src="/files/frVWEpbDu3tutq6lj8gp" alt=""><figcaption></figcaption></figure>

Finally, if you want to manage your friends list, click the **Friends** icon in the Quick navigation menu at the bottom right of your screen:

<figure><img src="/files/0X4HckxoLAjZ2aAbu0GL" alt=""><figcaption></figcaption></figure>

Here you'll be able to see all your friends, as well as the one you just added!


# Direct messages

Direct messages are messages between **two people**. You **can not** start a direct message with an agent, you can only have agent chats. Each agent chat is its own context thread, which is helpful for managing agent memory, but people do not have the same limitations.

There are two ways to send a direct message;

* Send it from your friends page
* Click on a user in channel or chat you both participate in and then **\[ Send Direct Message ]**

<figure><img src="/files/frVWEpbDu3tutq6lj8gp" alt=""><figcaption></figcaption></figure>

That's it! Direct messages are easy.


# Private and group chats

Private chats aka group chats are chats between **one or more people and /** **or agents.**

* Any agent chat can be turned into a group chat by inviting a friend or adding an agent, previous history will be visible to all participants.
* Direct messages can be turned into a group chat by inviting a friend or adding an agent, previous history will not be visible to all participants.
* There is a maximum of 32 participants including users and agents.

You can invite friends or agents to an existing chat by;

* Inviting via the **\[ . . . ]** settings button on a chat in the active chats list
* Inviting via the quick invite button in the active chat title bar

In order to get agents to respond in group chats, **they must be specifically @-mentioned**.

<figure><img src="/files/ICnly38wSONStsl3wH5I" alt=""><figcaption></figcaption></figure>


# Organizations (teams)

Superuser has support for organizations (teams), just like Slack and Discord.

* On Slack these are called **workspaces**
* On Discord these are called **servers**
* On Superuser we call them **organizations**, but they're all basically the same thing!

You can find a list of organizations you're part of on the left sidebar in the organizations list. If you're not part of any yet, you can try creating your own!

## Creating an organization

To create an organization, click the **\[ + ]** button at the bottom of your organizations list, which is on the very left of the screen.

<figure><img src="/files/63Y5RwvIpY0PSYmksXAj" alt=""><figcaption></figcaption></figure>

You'll be prompted to enter;

* Display name
* Unique identifier
* Community type

<figure><img src="/files/SaHkxRxSPXAfbF4YwBe2" alt=""><figcaption></figcaption></figure>

Your **community type** determines how billing for agents works.

* **Organization** community types are meant for businesses
  * You, the creator, will pay for all inference (AI usage) in this organization.
  * Each agent message sent in the organization counts towards the organization total or is billed from the organization's credits.
* **Community** community types are meant for groups of friends and community-supported endeavors
  * Each member pays for inference (AI usage) inside the organization from their own personal account.
  * Each agent message sent in the organization counts towards the *requester's* total or is billed from the *requester's* credits.


# Inviting new members

To invite new members to an organization, **create invites for any public channel**. You can do this from the **\[ . . . ]** button next to the channel name:

<figure><img src="/files/Vrih5CAbtVrtowWnBe2c" alt=""><figcaption></figcaption></figure>

Or in the channel title bar:

<figure><img src="/files/IlOBm0nlx7KahYT7j1EH" alt=""><figcaption></figcaption></figure>

You can then send invites via email directly, or copy and paste a link:

<figure><img src="/files/oNAtge8JEYxzzdyiOqeI" alt=""><figcaption></figcaption></figure>


# Public channels

Public channels are visible to **every member of the organization**.

* Everybody is a participant and everybody can chat.
* All agents added to the organization can be used in a public channel.
* You can create a new public channel at any time by clicking the **\[ + ]** button next to your channels list.

<figure><img src="/files/fnaKgSErCOirBsIEsFI0" alt=""><figcaption></figcaption></figure>


# Private channels

Private channels are visible to **invited participants only**.

* There is a maximum of 32 participants.
* Both agents and users count towards this limit.
* You can create a new private channel at any time by clicking the **\[ + ]** button next to your channels list and turning **Private channel** to **ON**.
* You can invite friends or agents to a private channel by;
  * Inviting via the **\[ . . . ]** settings button on a chat in the active chats list
  * Inviting via the quick invite button in the active chat title bar

<figure><img src="/files/N3DR5RPccMpcz4aKBcna" alt=""><figcaption></figcaption></figure>


# Private and group chats

Private aka group chats work the same way in organizations as they do on your personal account.

* To create a new private chat, click the **\[ New chat ]** button in your organization view, then start a chat with an agent to create an agent chat for the organization.
* Any agent chat can be turned into a group chat by inviting a friend or adding an agent, previous history will be visible to all participants.
* There is a maximum of 32 participants including users and agents.

<figure><img src="/files/yPl6OP6wSu7j1p9mYYyu" alt=""><figcaption></figcaption></figure>


# Emojis and reactions

Emojis are currently supported in Superuser via unicode; e.g. if your device lets you insert and display an emoji, it'll appear!

<figure><img src="/files/DGUMbzzNbvhKTOeGNY1Y" alt=""><figcaption></figcaption></figure>

Reactions can be added via the quick reply menu. Simply hover over any message on desktop / web, or press-and-hold on mobile:

<figure><img src="/files/bSx7q6hdWVUHi2wrMzyV" alt=""><figcaption></figcaption></figure>

By default we support ❤️, 👍 and 💯 as quick reactions. You can click the smiley face with a plus to view our full reaction emoji list:

<figure><img src="/files/xI8SecbEKkGO1g3I0sw1" alt=""><figcaption></figcaption></figure>

Here you can select your favorite emoji, search for emojis, and change emoji skin tone.


# /me emotes (actions)

As a throwback to the IRC days, Superuser supports /me emotes (actions). These are messages that appear as *actions* instead of just messages. They are just a fun wrapper around normal messages. You'll find that agents in particular like to participate: they will *always* respond to /me emotes with their own emotes.

* /me emotes work just like regular messages: uploads, tagging, etc.
* Agents can still use tools, append attachments and more when responding with /me emotes

<figure><img src="/files/KL49DEGBk9AVlrqkbd6V" alt=""><figcaption></figcaption></figure>


# Getting started with agents

An agent on Superuser is a scaffold for an AI assistant that contains;

* A **profile**
* A **language model**
* An **instruction prompt**
* Context in the form of **markdown**, **documents** and **websites**
* A recommended **set of tools**

However, **any agent can install any tool**. The "agent" itself is just a proprietary combination of { profile, LLM, prompt, context }.

## Adding agents to your organization

In order to talk to agents, or allow other members of your organization to chat freely with agents, you'll need to first **add them to your organization**.

First, find an agent you want to add by clicking on the **\[ Agents ]** link under the primary actions for your organization.

<figure><img src="/files/Y5zWErjAo6Pd9W3ZnkDK" alt=""><figcaption></figcaption></figure>

You'll see three tabs at the top of this page:

* My agents
  * Agents you have added to your organization (or personal account, depending on whether you are on your organization view or personal workspace)
* Verified
  * Agents that have been created by the community vetted and approved by the Superuser team
* Community
  * Agents that have been created by the community but have not been vetted

<figure><img src="/files/jCWLRewtnjvh7z0yH2WB" alt=""><figcaption></figcaption></figure>

If you haven't created any agents yet, you'll want to peruse the **Verified** or **Community** sections.

Now, find an agent, and click on it to select it and read more about it. Once you've decided that you want to add this agent, just start a conversation with it! There's a button to start a conversation at the bottom of the screen on every agent page.

<figure><img src="/files/Iu0EARByZazv1hkcVfPO" alt=""><figcaption></figcaption></figure>

Click the **\[ Chat with ... ]** button and send a message to add the agent. You **must** send a message for the agent to be added to your organization properly.


# Installing tools

Once an agent has been added to your organization or personal account, you can modify which tools it has available and even add new tools.

**Any agent can install any tool.** If you like an agent that somebody else has built, you can append your own tools to it to extend it. To modify tools for an agent you've installed, select the agent from the **My agents** to view its profile:

<figure><img src="/files/hhn4jMMRLNnUO1FlEW6J" alt=""><figcaption></figcaption></figure>

Click on the **Toolkits** tab to manage tools. Here you can **Uninstall** existing tools or **Install** new tools. Try out any combination you'd like!

<figure><img src="/files/zx8Wnd4pskVS6fs6c51Q" alt=""><figcaption></figcaption></figure>

Note that some tools are **Premium**, meaning they cost credits to run. You'll be notified before you install and asked to approve the installation. **You will not be charged for installing a Premium tool, only using it.**


# Secrets and security

Some tools require **secrets** like API keys or database passwords in order to be used correctly.

* Only **open source** tools can request secrets
* You can **always** inspect the code that you share your secrets with
* If package code changes, **your installation will be invalidated**, so your agent will **never** run untrusted code with your secrets
* Secrets are stored on an **API keychain** which only exposes secrets **specifically requested** by the tool, which is set by the developer.

To manage secrets, first find a package that requires secrets like **PostgreSQL**:

<figure><img src="/files/3apSVEeAjZ4uOvtHDVml" alt=""><figcaption></figcaption></figure>

Click **\[ Install ]**. If the package requires secrets, a new **Keychain** tab will appear:

<figure><img src="/files/vBpFhjPE5CZlmdPjfCPN" alt=""><figcaption></figcaption></figure>

Click this tab to view your API keychain.

<figure><img src="/files/PLk4xtvLZsRNEVxUON4N" alt=""><figcaption></figcaption></figure>

Here you can save your secrets. **If a secret is not required, just save the empty textbox**. For example, in the PostgreSQL example above, PG\_SSH\_TUNNEL\_URL and PG\_SSH\_TUNNEL\_PRIVATE\_KEY can be saved as empty strings if the connection URL does not require a proxy.


# Removing agents

Removing an agent from your organization is simple. Just go to **\[ Agents ] > \[ My agents ] >** select the agent you wish to remove. Scroll down on its **Profile** tab to find **Chat management**:

<figure><img src="/files/aoeGYQtxFkVzyWGz5ssa" alt=""><figcaption></figcaption></figure>

Click **\[ Remove from chat ]** and voila, this agent has been removed from the organization.


# Creating a custom agent

You may want to create a custom agent for a few reasons;

* To give an agent unique personality
* To give an agent a proprietary instruction prompt to improve performance in a specific domain
* To give an agent access to proprietary context in the form of markdown, documents or websites

Creating a custom agent on Superuser is simple! Just go to your **Agents** page from your personal workspace or the organization you want to create the agent for.

<figure><img src="/files/8ZohUT7LfA4sjLH6KHzt" alt=""><figcaption></figcaption></figure>

Once there, click the **\[ + New agent ]** button in the top right. You'll be prompted to name your agent:

<figure><img src="/files/YuMiLPAFhteyGdOrHJxn" alt=""><figcaption></figcaption></figure>

Choose any name and click **\[ Continue ]**. Next, you'll be asked to provide an **instruction prompt**. This guides how your agent responds to messages. It can be changed at any time and we automatically generate one for you to begin with, so don't worry too much about it.

<figure><img src="/files/ZBqY2OFT0y2jvK5EFbCg" alt=""><figcaption></figcaption></figure>

Once you're satisifed with your prompt, click **\[ Okay ... ]** to continue. You'll now be given the chance to link your custom agent to Discord. It is not necessary to link your agent to Discord and it can be done at any time in the future.

<figure><img src="/files/keihHRPoLXPKOJ3ncNIH" alt=""><figcaption></figcaption></figure>

You can Link or Skip, it's up to you. Once you proceed, you'll be shown your Agent's profile card.

<figure><img src="/files/QtgJ74cL2aERwxP3PefD" alt=""><figcaption></figcaption></figure>

From here you can **\[ Edit agent ]** to modify your agent's profile, language model, prompt, context, tools and Discord link — or you can just start chatting!


# Modifying your agent

Once you've created a custom agent, you can modify it at any time via the **\[ My agents ]** tab on your **\[ Agents ]** page for your personal workspace or organization. You have a few options:

* [Profile](/agents/modifying-your-agent/profile)
  * Changing name and discoverability
* [Agent](/agents/modifying-your-agent/agent-settings)
  * For modifying language model, prompt and agent harness
* [Context](/agents/modifying-your-agent/context-markdown-websites-pdf)
  * Managing markdown, documents and websites
* [Toolkits](/agents/modifying-your-agent/installing-tools)
  * Installing and managing tools
* [Keychain](/agents/modifying-your-agent/secrets-and-security)
  * Managing secrets for open source tools
* [Connections](/agents/modifying-your-agent/third-party-connections)
  * Linking to Discord


# Profile

Your agent profile page shows a brief summary of your agent, including the number of messages it has processed and how many organizations it is active in. On this page you can:

* [Change your agent's profile](#change-your-agents-profile)
* [Remove from chat or archive agent](#remove-from-chat-or-archive-agent)

<figure><img src="/files/6ElkYPFNETb3VUosKMsZ" alt=""><figcaption></figcaption></figure>

## Change your agent's profile

To change your agent's profile, simply scroll down on this tab. You'll see **Avatar**, **Display name**, **Username**, **About me** and **Discoverability**.

<figure><img src="/files/Nq2aGTKZ1dPqdeWY1gXu" alt=""><figcaption></figcaption></figure>

* Avatar
  * Upload any image, we will automatically resize and center the image for you
* Display name
  * How your agent's name appears in chat
* Username
  * How you can @-mention your agent
  * Usually the UI handles this for you
  * It will always be prefixed with `@yourusername+` or `@orgname+`
* About me
  * A nice description that tells your teammates what the agent is for or encourages other users to take a look, if public
* Discoverability
  * Who can see and chat with this agent?
  * Default is **Private**, only you or the organization it was created in
  * **Unlisted** won't show up in the Community tab, but you can direct link others
  * **Public** shows up in the community tab

## Remove from chat or archive agent

Scrolling a little further will bring you to **Chat management**. Here you can archive your agent (if you created it) or remove it from chat if you simply added somebody else's public agent.

<figure><img src="/files/DsN9AUbC4qWBinAwSAEV" alt=""><figcaption></figcaption></figure>


# Agent settings

Your agent settings are accessible via the **Agent** tab once you've selected an agent you want to modify.

<figure><img src="/files/oC73MBeCpbIcGepajWTK" alt=""><figcaption></figcaption></figure>

* Language model
  * Choose which LLM will power your agent and agent experience
  * By default, requests made to agents will first go through your subscription limit, then will be billed per token from your credits
* Instruction prompt
  * Basic instructions and guidelines your agent should follow, for developers this is equivalent to a system prompt when using an API. Markdown is supported here but not auto-formatted.
* Agent harness
  * Currently WIP. Right now the agent will only run "one turn" of interaction to make single function calls or multiple function calls in parallel based on the user request. We are working on new harnesses for advanced functionality.


# Context: Markdown, Websites, PDF

The **Context** tab has three sub-tabs: **Notes**, **Documents** and **Websites**.

<figure><img src="/files/wiiBEnMYIpizk71bztiy" alt=""><figcaption></figcaption></figure>

* Notes
  * Markdown documents
  * Included in **every request**
  * Can be used to expand upon prompt in a configurable way
  * Can be turned off at any time without deleting
* Documents
  * Upload documents to give agent smarter context
  * Undergoes **retrieval**, e.g. not included in every request by default: compared to and ranked against user request by similarity
  * Retrieval system automatically batches and groups relevant sections together&#x20;
* Websites
  * Similar to documents, website is crawled for individual pages that act like documents
  * Undergoes **retrieval**, e.g. not included in every request by default: compared to and ranked against user request by similarity
  * Retrieval system automatically batches and groups relevant sections together&#x20;


# Installing tools

Tools can be installed to any agent regardless of whether or not they are a custom agent.

* See: [Getting started with agents > Installing tools](/agents/getting-started-with-agents/installing-tools)


# Secrets and security

Managing secrets and security for custom agents is the same as public agents.

* See: [Getting started with agents > Secrets and security](/agents/getting-started-with-agents/secrets-and-security)


# Third-party connections

Right now, Superuser supports **linking agents to Discord**. It is easy and takes less than a minute, but there are a few limitations;

* Only **one** agent can be linked per Discord server via our official app
* You may be rate limited if you try to change your agent name too frequently, just be patient and try again later
* If you want multiple agents in a server, use Superuser [Organizations](/team-chat/organizations-teams) instead

To link an agent to Discord, go to **\[ Agents ] > \[ My agents ]**, find your agent, the select the **Connections** tab.

<figure><img src="/files/byt0NIYqRrBEjzh77F7T" alt=""><figcaption></figcaption></figure>

Click on **\[ Link a new Discord server ]**. You'll be prompted by Discord to select a server and continue:

<figure><img src="/files/Wu7DjaA6RYh0CHCezE1S" alt=""><figcaption></figcaption></figure>

Follow Discord's instructions, click continue, and your agent will be added to your Discord server. It's that easy!


# Publishing your agent for others

You can publish your agent for others on the **\[ Agents] > \[ Community ]** tab. To publish here, you must modify your agent's **Discoverability** setting on its profile. There are three settings:

* Default is **Private**, only you or the organization it was created in
* **Unlisted** won't show up in the Community tab, but you can direct link others
* **Public** shows up in the community tab
* See: [Modifying your agent > Profile](/agents/modifying-your-agent/profile) for more information


# Linking to Discord

To link to Discord, visit **\[ Agents ] > \[ My agents ]**, find your agent, and click **Connections**.

* See: [Modifying your agent > Third-party connections](/agents/modifying-your-agent/third-party-connections)


# Archiving your agent

To archive your agent, visit **\[ Agents ] > \[ My agents ]** then find your agent and select its **Profile**. Scroll to the bottom to find **Chat management** then **Archive your agent**.

* See: [Modifying your agent > Profile > Remove from chat or archive agent](/agents/modifying-your-agent/profile#remove-from-chat-or-archive-agent)


# Installing tools

Tools can be installed to any agent regardless of whether or not they are a custom agent. You **must** install tools by managing your agent directly.

* See: [Getting started with agents > Installing tools](/agents/getting-started-with-agents/installing-tools)


# Using tools in chat

Using tools in chat is easy. Once you've installed tools to an agent, just ask it to perform a task that would use a tool!

* [Using a tool](#using-a-tool)
* [Inspecting tool details](#inspecting-tool-details)
* [How compute credits work](#how-compute-credits-work)

## Using a tool

Here's an example of generating an image:

<figure><img src="/files/5Hw3XsxunvqfXTl2zNR5" alt=""><figcaption></figcaption></figure>

Here I've asked an agent with the [Nano Banana 2](https://superuser.app/org/superuser/toolkits/nano-banana-2) toolkit to:

> Hey can you generate a picture of two palm trees high-fiving?

And that's all it took to work!

## Inspecting tool details

Once a tool has run, you can click on it to expand and see details.

<figure><img src="/files/UbXoBTdVVIODHTtfvmek" alt=""><figcaption></figcaption></figure>

In this case I can see;

* The tool that was requested
* The compute credit fee (in this case, 6.8 credits + 0.5 credits of compute)
* The arguments provided to the API endpoint (all tools are APIs!)
* All files are saved as attachments, any raw data output will be shown as JSON

## How compute credits work

In the example above, you'll notice the request was billed for **Premium** + **Compute**.

* **Premium** is developer-defined compute pricing, intended to cover costs of performing the task. You'll see these charges with things like media generation.
* **Compute** is platform-defined, and is billed at 50 credits per 1,000 GB-s of usage. 1 GB-s of usage = 1GB of RAM in use for 1 second. You can read more on our [pricing page](https://superuser.app/pricing).


# Publishing tools via command line

Tools are published via our command line tools.

* [Installing `sup`: Superuser package manager](#installing-sup-superuser-package-manager)
* [Initialize a new Superuser package](#initialize-a-new-superuser-package)
* [Creating tools aka endpoints](#creating-tools-aka-endpoints)
* [Installing NPM packages](#installing-npm-packages)
* [Deploy a Superuser package](#deploy-a-superuser-package)
* [Additional utilities](#additional-utilities)
  * [Generate endpoints](#generate-endpoints)
  * [Generate tests](#generate-tests)
  * [Run tests](#run-tests)
  * [Environment variables](#environment-variables)

## Installing `sup`: Superuser package manager

Our command line tools are available at [github.com/superuserapp/superuser-cli](https://github.com/superuserapp/superuser-cli). For the most up to date guide on using the command line tools, please check the repository. This page exists as a quick getting started guide.

## Initialize a new Superuser package

To initialize a new Superuser package:

```sh
$ npm i superuser.app -g
$ mkdir new-package
$ cd new-package
$ sup init # sup is cli tool for superuser.app
```

You'll be walked through the process. The `sup` CLI will automatically check for updates to core packages, so make sure you update when available. To play around with your Superuser package locally;

```sh
$ sup serve
```

Will start an HTTP server. To execute a standalone endpoint / tool:

```sh
# run functions/index.js
$ sup run /

# run functions/some-endpoint.js
$ sup run some-endpoint

# run functions/index.js with {"name":"hello"} POST parameters
$ sup run / --name hello
```

## Creating tools aka endpoints

Defining custom tools is easy. You'll find the terms **tool** and **endpoint** used interchangeably as they all refer to the same thing: your bot executing custom code in the cloud.

A **tool** is just an **endpoint** hosted by the Superuser Package Registry.

All endpoints for Superuser packages live in the `functions/` directory. Each file name maps to the endpoint route e.g. `functions/hello.js` routes to `localhost:8000/hello`. You can export custom `GET`, `POST`, `PUT` and `DELETE` functions from every file. Here's an example "hello world" endpoint:

You can create a new endpoint with:

```sh
$ sup g:endpoint hello
```

```javascript
// functions/hello.js

/**
 * A basic hello world function
 * @param {string} name Your name
 * @returns {string} message The return message
 */
export async function GET (name = 'world') {
  return `hello ${name}`!
};
```

You can write any code you want and install any NPM packages you'd like to your tool package.

## Installing NPM packages

You can install NPM packages the traditional way, or using your bundler of choice:

```sh
$ npm i stripe --save # or whatever package you want
```

Superuser will **automatically install** NPM packages on deployment, we do not use your locally stored packages.

## Deploy a Superuser package

To deploy a public project to a `development` environment, you can use:

```
$ sup publish
```

You can also publish to `staging` and `production` using:

```
$ sup publish --env staging
$ sup publish --env production
```

## Additional utilities

There are a few additional utilities you may find useful with this package;

### Generate endpoints

```sh
# generates functions/my-endpoint/example.js
$ sup g:endpoint my-endpoint/example
```

### Generate tests

```sh
# Generate blank tests or ones for an endpoint
$ sup g:test my_test # OR ...
$ sup g:test --endpoint my-endpoint/example
```

### Run tests

You can write tests for your tools to verify they work. Simply run;

```sh
$ sup test
```

Your tests in the `test/` directory will be run top-down with shallow folders first, and alphabetically.

### Environment variables

{% hint style="danger" %}
Be careful when using environment variables with public packages.\
By default **we do not expose .env files** in public package code, so your secrets are safe and encrypted. However, all Superuser users can run public package code: it is recommended you do NOT log or return these variables.
{% endhint %}

You can store environment variables with your packages in the root directory as:

```
.env
.env.staging
.env.production
```

Your environment variables will then be available in code via `process.env.VAR_NAME`.

These files **will not** be published for everybody to see, so you can use them to hide secrets within your code. However, be careful when using environment variables with public packages: if you ever return them in an endpoint response, or connect to sensitive data, there's a chance you may expose that information to another user of the platform.


# Package specification

A guide to packages on Superuser

## Overview

All packages on Superuser are available as both **REST API servers** and **MCP servers**. They are hosted on `{package}.su.dev` and expose HTTP endpoints that act as tools. They are built using the [Instant API framework](https://gitub.com/instant-dev/api), a JavaScript framework and gateway that turns JavaScript functions into type-validated HTTP endpoints with built-in bindings for the MCP Streamable HTTP specification.

**REST** means **Re**presentational **S**tate **T**ransfer and is a fancy way of saying these servers expose endpoints that support multiple HTTP verbs: `GET`, `POST`, `PUT`, `DELETE`.

**MCP** stands for **M**odel **C**ontext **P**rotocol and is an emerging standard for connecting LLMs to remote procedure calls. It is currently supported by OpenAI, Anthropic and Google Gemini APIs.

## Quick examples

For an example of what hosted tools look like in production, visit:

* [Current weather package](https://superuser.app/toolkits/@keith/weather)
* [Stripe customer package](https://superuser.app/toolkits/@keith/stripe) (includes **required** keys)
* [GPT image generator package](https://superuser.app/toolkits/@keith/openai-gpt-image) (outputs image)

## Instant API

[Instant API](https://github.com/instant-dev/api) is a combined framework and HTTP gateway for turning JavaScript functions into typed HTTP endpoints. It uses JSDoc comments above function exports to automatically generate OpenAPI and JSON schemas for each endpoint, as well as tool definitions. **It is vendor-agnostic**, meaning Instant API projects can be hosted anywhere like Vercel, AWS, GCP, etc.

### Project history

Instant API is part of a [broader JavaScript framework + ORM called instant.dev](https://github.com/instant-dev/instant.dev). That project itself is a derivative of [Nodal](https://github.com/keithwhor/nodal), an now-defunct API framework we first built in 2015. Instant.dev is our "Ruby on Rails for JavaScript", with over 10 years of development time behind it. We have used it to build dozens of products and the most successful have handled over 1B API requests per week. In an era of LLM extensibility, we are really excited about its potential to generate tools for LLMs quickly and easily.

## Package hosting

The Superuser package registry automatically hosts and scales your Instant API projects for you as packages. However, Instant API projects can be hosted on any infrastructure provider that supports Node.js 20.x or above: like Vercel, Railway and AWS. This means projects that you build on Superuser are *transportable*. When you build tools for Superuser you are not locked in to using us as a hosting provider, though we do manage secret storage, package verification and more.

### Our registry domain

All packages for Superuser are hosted on our gateway at `{package}.su.dev`

### Authentication and making requests

You authenticate into packages using **API keychains**, a primitive we have created for securely storing, managing and delegating access to third-party secrets and auth.

These keychains (1) authenticate you as an Superuser user and (2) can scaffold one or more API secrets that can be shared with packages. For security considerations, please read the [API keychain specification](/hosted-tools/publishing-tools-via-command-line/api-keychain-specification).

```sh
curl https://{package}.su.dev/endpoint-name \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {API_KEYCHAIN_SECRET}' \
  --data '{"some":"json"}'
```

## Package directory structure

Every package contains a directory structure with the following format:

```sh
functions/
  index.js            # root endpoint, path = /
  my-function.js      # endpoint, path = /my-function
  404.js              # catchall endpoint, path = /my-function/*
  subdir/
    index.js          # subdir root endpoint, path = /subdir
    do-thing.js       # endpoint, path = /subdir/do-thing
    404.js            # catchall endpoint, path = /subdir/*
instant.package.json  # superuser.app package info
package.json          # traditional node.js package.json
```

As you can see, Superuser packages use **file-based routing**. Everything exported from the `functions/` folder is available as an API endpoint.

## superuser.json

This is your Superuser package configuration. It typically has the following format;

```json
{
  "name": "@user/package",
  "timeout": 10
}
```

Where `"name"` is the package name, usually of the format `@user/package` for public packages. Agent code packages, which are private, are of the format `agent/@user/package`.

### Changing timeout

The default timeout for Superuser packages is 10 seconds. This means tools that are part of this package will run for 10 seconds before automatically halting execution. You can manually change this value to any number between 1 (1 second) and 300 (5 minutes).

### Setting required keychain keys

Public packages can request secret keys from API keychains. For example, the [Stripe customers package](https://superuser.app/toolkits/@keith/stripe) requires a **STRIPE\_SECRET\_KEY** to use it successfully. To set these per package, add them to your `superuser.json` like so:

```json
{
  "name": "@user/package",
  "timeout": 10,
  "keychain": {
    "required": [
      {
        "name": "STRIPE_SECRET_KEY",
        "description": "Your Stripe secret key, available at dashboard.stripe.com/apikeys
      }
    ]
  }
}
```

## Package endpoints

Every file in the `functions/` directory is used to create an HTTP API endpoint that can respond to `GET`, `POST`, `PUT` and `DELETE` requests. Each of these endpoints is available to your agents for use as a tool.

Here's an example of a weather endpoint that is accessible via HTTP GET.

```javascript
/**
 * Retrieve the weather for a specific location
 * @param {?string{1..64}} location Search by location
 * @param {?object} coords Provide specific latitude and longitude
 * @param {number{-90,90}} coords.lat Latitude
 * @param {number{-180,180}} coords.lng Longitude
 * @param {string[]} tags Nearby locations to include
 * @returns {object} weather Your weather result
 * @returns {number} weather.temperature Current temperature of the location
 * @returns {string} weather.unit Fahrenheit or Celsius
 */
export async function GET (location = null, coords = null, tags = []) {

  if (!location && !coords) {
    // Prefixing an error message with a "###:" between 400 and 404
    // automatically creates the correct client error:
    // BadRequestError, UnauthorizedError, PaymentRequiredError,
    // ForbiddenError, NotFoundError
    // Otherwise, will throw a RuntimeError with code 420
    throw new Error(`400: Must provide either location or coords`);
  } else if (location && coords) {
    throw new Error(`400: Can not provide both location and coords`);
  }
  
  // Fetch your own API data
  await getSomeWeatherDataFor(location, coords, tags);
  
  // mock a response
  return { temperature: 89.2 units: `°F` };
  
}
```

### Using different HTTP methods

A single file can output up to **four** different endpoints corresponding to an HTTP method, and each can be used as an individual tool. `GET`, `POST`, `PUT` and `DELETE` are all supported.

For simple tools, exporting a `default` function will always work as an endpoint that responds to **all HTTP methods**.

```javascript
/**
 * A simple hello world endpoint, responds to all HTTP methods
 */
export default async function () {
  return `hello world!`;
}
```

You can `GET`, `POST`, `PUT` or `DELETE` to this endpoint. If you'd like to know which method was called, you can use the magic `context` object to check the HTTP method:

```javascript
/**
 * A simple hello world endpoint, responds to all HTTP methods
 */
export default async function (context) {
  return `hello world, method is ${context.http.method}`;
}
```

If you want different functionality depending on the HTTP method, it's best to export named functions for `GET`, `POST`, `PUT` and `DELETE`.

```javascript
/**
 * A simple hello world endpoint, responds to GET
 */
export async function GET () {
  return `Hello HTTP GET!`;
}

/**
 * A simple hello world endpoint, responds to POST
 */
export async function POST () {
  return `Hello HTTP POST!`;
}

/**
 * A simple hello world endpoint, responds to PUT
 */
export async function PUT () {
  return `Hello HTTP PUT!`;
}

/**
 * A simple hello world endpoint, responds to DELETE
 */
export async function DELETE () {
  return `Hello HTTP DELETE!`;
}
```

If you don't export a specific HTTP method, requests to that method will fail with a `501: Not implemented` error.

### Endpoint arguments

To create arguments for your endpoint, you simply add arguments to your function signature and comment them using JSDoc.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 * @param {number} age
 */
export async function POST (name, age) {
  return `Hello ${name}, you are ${age}!`;
}
```

When you make an HTTP POST request to this endpoint, you can provide an `application/json` payload with `{"name":"test","age":20}` and will receive the result `"Hello test, you are 20!"`.

#### Required arguments

To set arguments as **required**, simply **do not** provide a default value for the argument in the function signature.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 */
export async function POST (name) {
  return `Hello ${name}!`;
}
```

If you try to send an HTTP POST with an empty payload, you will receive the following response:

```json
{
  "type": "ParameterError",
  "message": "Invalid parameter \"name\": required",
  "details": {
    "name": {
      "message": "required",
      "required": true
    }
  }
}
```

#### Optional arguments

To mark an argument as optional, simply give it a default value. A default value **must** be either `null` or match the type specified by JSDoc.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 */
export async function POST (name = 'world') {
  return `Hello ${name}!`;
}
```

Sending an empty payload will now result in a `"Hello world!"` response.

#### The context argument

Every function signature can include an optional `context` argument. The context argument must **always be the last argument** when provided and **must not be documented** by JSDoc.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 */
export async function POST (name = 'world', context) {
  return context;
}
```

The `context` object contains contextual runtime data, like the HTTP method, raw HTTP body, parameters and more.

* `context.name` the function name
* `context.path` an array of path segments used to run the endpoint
* `context.params` a SON representation of arguments passed in to the endpoint
* `context.remoteAddress` the IPv4 or IPv6 address that requested the endpoint
* `context.uuid` a unique execution id
* `context.http.method` the HTTP method used to invoke the endpoint
* `context.http.headers` the HTTP headers used to invoke the endpoint
* `context.http.body` a raw utf-8 string of the HTTP POST body, if applicable
* `context.keychain.key("MY_KEY")` a method to access API keychain keys provided to the endpoint at runtime

### Endpoint returns: JSON, custom HTTP responses and files

Similar to arguments, you can specify return types for endpoints. By default, the return type is `any`. We will allow any return type, though there are a few special cases.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 */
export async function POST (name) {

  // basic return types
  return `Hello ${name}!`; // returns JSON string: "Hello world"
  return 23; // returns JSON number: 23
  return true; // returns JSON boolean: true
  return false; // returns JSON boolean: false
  return null; // returns JSON: null
  return void 0; // undefined, coerced to return JSON: null
  return undefined; // undefined, coerced to return JSON: null
  return ['some', 'array']; // returns JSON: ["some","array"]
  return {some: 'object'}; // returns JSON: {"some":"object"}
  
  // custom http responses
  // this will return the exact http response described
  return {
    statusCode: 200,
    headers: {'Content-Type': 'text/plain'},
    body: Buffer.from('What')
  };
  
  // file responses
  const file = Buffer.from('...');
  file.contentType = 'image/png'; // optional: sets Content-Type header
  return file; // returns raw buffer as an HTTP response
  
  // nested files in JSON objects
  const file = Buffer.from('...');
  return { file }; // returns JSON: {"file":{"_base64":"b64_value"}}
  
}
```

You can specify endpoint `returns` types with the `@returns` directive:

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 * @returns {string}
 */
export async function POST (name = 'world') {
  returns `Hello ${name}!`;
}
```

By specifying the return type, you can force our gateway to do type validation on responses as well: if something goes wrong, we can throw an error. For example, in this code the return type is specified as **number**, but it's returning a string.

```javascript
/**
 * Generates a hello world message
 * @param {string} name
 * @returns {number}
 */
export async function POST (name = 'world') {
  returns `Hello ${name}!`;
}
```

We'll get the following error.

```json
{
  "type": "ValueError",
  "message": "The value returned by the function did not match the specified type",
  "details": {
    "returns": {
      "message": "invalid return value: \"Hello world!\" (string), expected (number)",
      "invalid": true,
      "expected": {
        "type": "number"
      },
      "actual": {
        "value": "Hello world!",
        "type": "string"
      }
    }
  }
}
```

## Returning attachments in the Superuser UI

To have a package return attachments in the Superuser UI, simply make sure you set the return type to `buffer` like so:

```javascript
import fs from 'fs';

/**
 * Returns an image that gets embedded in chat
 * @returns {buffer}
 */
export async function POST () {
  const file = fs.readFileSync('my-image.png');
  file.contentType = 'image/png';
  return file;
}
```

By setting the `@returns` directive you tell the OpenAPI spec and tool schema that we expect to return a file. The `image/png` content type header gets read from the `.contentType` field of the buffer. This content type will direct the attachment within the UI to render an image.

## Custom errors

To throw custom errors from an endpoint you have two options.

### 1. Throw a 400 to 404 error

```javascript
export default async function () {
  // Prefixing an error message with a "###:" between 400 and 404
  // automatically creates the correct client error:
  // BadRequestError, UnauthorizedError, PaymentRequiredError,
  // ForbiddenError, NotFoundError
  // Otherwise, will throw a RuntimeError with code 420
  throw new Error(`400: Some error`); // BadRequestError, code 400
  throw new Error(`401: Unauthed!`); // UnauthorizedError, code 401
  throw new Error(`402: Pay me`); // PaymentRequiredError, code 402
  throw new Error(`403: Nope`); // ForbiddenError, code 403
  throw new Error(`404: Where'd it go?`); // NotFoundError, code 404
  throw new Error(`Oh no!`); // RuntimeError, code 420
}
```

### 2. Return a custom HTTP response

```javascript
export default async function () {
  return {
    statusCode: 500,
    headers: {},
    body: Buffer.from(`My custom 500 error`)
  };
}
```

## Type validation

You get tool type validation for free when building packages with Superuser as part of the [Instant API](https://github.com/instant-dev/api) framework. You can define types for both `@param` and `@returns` arguments in the function signature. Instant API supports the following types.

### Supported types

| Type        | Definition                                                                                     | Example Parameter Input Values (JSON)                                                                                                         |
| ----------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| boolean     | True or False                                                                                  | `true` or `false`                                                                                                                             |
| string      | Basic text or character strings                                                                | `"hello"`, `"GOODBYE!"`                                                                                                                       |
| number      | Any double-precision [Floating Point](https://en.wikipedia.org/wiki/IEEE_floating_point) value | `2e+100`, `1.02`, `-5`                                                                                                                        |
| float       | Alias for `number`                                                                             | `2e+100`, `1.02`, `-5`                                                                                                                        |
| integer     | Subset of `number`, integers between `-2^53 + 1` and `+2^53 - 1` (inclusive)                   | `0`, `-5`, `2000`                                                                                                                             |
| object      | Any JSON-serializable Object                                                                   | `{}`, `{"a":true}`, `{"hello":["world"]}`                                                                                                     |
| object.http | An object representing an HTTP Response. Accepts `headers`, `body` and `statusCode` keys       | `{"body": "Hello World"}`, `{"statusCode": 404, "body": "not found"}`, `{"headers": {"Content-Type": "image/png"}, "body": Buffer.from(...)}` |
| array       | Any JSON-serializable Array                                                                    | `[]`, `[1, 2, 3]`, `[{"a":true}, null, 5]`                                                                                                    |
| buffer      | Raw binary octet (byte) data representing a file.                                              | `{"_bytes": [8, 255]}` or `{"_base64": "d2h5IGRpZCB5b3UgcGFyc2UgdGhpcz8/"}`                                                                   |
| any         | Any value mentioned above                                                                      | `5`, `"hello"`, `[]`                                                                                                                          |

### Type coercion when making HTTP requests

The `buffer` type will automatically be converted to a `Buffer` from any `object` with a **single key-value pair matching the footprints** `{"_bytes": []}` or `{"_base64": ""}`.

Otherwise, parameters provided to a function are expected to match their defined types. Requests made over HTTP GET via query parameters or POST data with type `application/x-www-form-urlencoded` will be automatically converted from strings to their respective expected types, when possible.

Once converted, all types will undergo a final type validation. For example, passing a JSON `array` like `["one", "two"]` as a string to a parameter that expects an `object` will convert from string to JSON successfully but fail the `object` type check, as it is an array.

| Type        | Conversion Rule                                                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------ |
| boolean     | `"t"` and `"true"` become `true`, `"f"` and `"false"` become `false`, otherwise will be kept as string |
| string      | No conversion: already a string                                                                        |
| number      | Determine float value, if NaN keep as string, otherwise convert                                        |
| float       | Determine float value, if NaN keep as string, otherwise convert                                        |
| integer     | Determine float value, if NaN keep as string, otherwise convert: may fail integer check                |
| object      | Parse as JSON, if invalid keep as string, otherwise convert: may fail object check                     |
| object.http | Parse as JSON, if invalid keep as string, otherwise convert: may fail object.http check                |
| array       | Parse as JSON, if invalid keep as string, otherwise convert: may fail array check                      |
| buffer      | Parse as JSON, if invalid keep as string, otherwise convert: may fail buffer check                     |
| any         | No conversion: keep as string                                                                          |

### Combining types

You can combine types using the pipe `|` operator. For example;

```javascript
/**
 * @param {string|integer} myparam String or an integer
 */
export async function GET (myparam) {
  // do something
} 
```

Will accept a `string` or an `integer`. Types defined this way will validate against the provided types in order of appearance. In this case, since it is a GET request and all parameters are passed in as strings via query parameters, `myparam` will **always** be received a string because it will successfully pass the string type coercion and validation first.

However, if you use a POST request:

```javascript
/**
 * @param {string|integer} myparam String or an integer
 */
export async function POST (myparam) {
  // do something
} 
```

Then you can pass in `{"myparam": "1"}` or `{"myparam": 1}` via the body which would both pass type validation.

You can combine as many types as you'd like:

```jsdoc
@param {string|buffer|array|integer}
```

Including `any` in your list will, as expected, override any other type specifications.

### Enums and restricting to specific values

Similar to combining types, you can also include specific JSON values in your type definitions:

```javascript
/**
 * @param {"one"|"two"|"three"|4} myparam String or an integer
 */
export async function GET (myparam) {
  // do something
} 
```

This allows you to restrict possible inputs to a list of allowed values. In the case above, sending `?myparam=4` via HTTP GET **will** successfully parse to `4` (`Number`), because it will fail validation against the three string options.

You can combine specific values and types in your definitions freely:

```jsdoc
@param {"one"|"two"|integer}
```

Just note that certain combinations will invalidate other list items. Like `{1|2|integer}` will accept any valid integer.

### Sizes (lengths)

The types `string`, `array` and `buffer` support sizes (lengths) via the `{a..b}` modifier on the type. For example;

```jsdoc
@param {string{..9}}  alpha
@param {string{2..6}} beta
@param {string{5..}}  gamma
```

Would expect `alpha` to have a maximum length of `9`, `beta` to have a minimum length of `2` but a maximum length of `6`, and `gamma` to have a minimum length of `5`.

### Ranges

The types `number`, `float` and `integer` support ranges via the `{a,b}` modifier on the type. For example;

```jsdoc
@param {number{,1.2e9}} alpha
@param {number{-10,10}} beta
@param {number{0.870,}} gamma
```

Would expect `alpha` to have a maximum value of `1 200 000 000`, `beta` to have a minimum value of `-10` but a maximum value of `10`, and `gamma` to have a minimum value of `0.87`.

### Arrays

Arrays are supported via the `array` type. You can optionally specify a schema for the array which applies to **every element in the array**. There are two formats for specifying array schemas, you can pick which works best for you:

```jsdoc
@param {string[]}      arrayOfStrings1
@param {array<string>} arrayOfStrings2
```

For multi-dimensional arrays, you can use nesting:

```jsdoc
@param {integer[][]}           array2d
@param {array<array<integer>>} array2d_too
```

**Please note**: Combining types are not currently available in array schemas. Open up an issue and let us know if you'd like them and what your use case is! In the meantime;

```jsdoc
@param {integer[]|string[]}
```

Would successfully define an array of integers or an array of strings.

### Object schemas

To define object schemas, use the subsequent lines of the schema after your initial object definition to define individual properties. For example, the object `{"a": 1, "b": "two", "c": {"d": true, "e": []}` Could be defined like so:

```jsdoc
@param {object}  myObject
@param {integer} myObject.a
@param {string}  myObject.b
@param {object}  myObject.c
@param {boolean} myObject.c.d
@param {array}   myObject.c.e
```

To define object schemas that are members of arrays, you must identify the array component in the property name with `[]`. For example:

```jsdoc
@param {object[]} topLevelArray
@param {integer}  topLevelArray[].value
@param {object}   myObject
@param {object[]} myObject.subArray
@param {string}   myObject.subArray[].name
```

### Parameter validation

The process for parameter validation takes the following steps:

1. Read parameters from the HTTP query string as type `application/x-www-form-urlencoded`
2. If applicable, read parameters from the HTTP body based on the request `Content-Type`
   * Supported content types:
     * `application/json`
     * `application/x-www-formurlencoded`
     * `multipart/form-data`
     * `application/xml`, `application/atom+xml`, `text/xml`
3. Query parameters **can not** conflict with body parameters, throw an error if they do
4. Perform type coercion on `application/x-www-form-urlencoded` inputs (query and body, if applicable)
5. Validate parameters against their expected types, throw an error if they do not match

During this process, you can encounter a `ParameterParseError` or a `ParameterError` both with status code `400`. `ParameterParseError` means your parameters could not be parsed based on the expected or provided content type, and `ParameterError` is a validation error against the schema for your endpoint.

### Query and Body parsing with `application/x-www-form-urlencoded`

Many different standards have been implemented and adopted over the years for HTTP query parameters and how they can be used to specify objects and arrays. To make things easy, Instant API supports all common query parameter parsing formats.

Here are some query parameter examples of parsing form-urlencoded data:

* Arrays
  * Duplicates: `?arr=1&arr=2` becomes `[1, 2]`
  * Array syntax: `?arr[]=1&arr[]=2` becomes `[1, 2]`
  * Index syntax: `?arr[0]=1&arr[2]=3` becomes `[1, null, 3]`
  * JSON syntax: `?arr=[1,2]` becomes `[1, 2]`
* Objects
  * Bracket syntax: `?obj[a]=1&obj[b]=2` becomes `{"a": 1, "b": 2}`
  * Dot syntax: `?obj.a=1&obj.b=2` becomes `{"a": 1, "b": 2}`
    * Nesting: `?obj.a.b.c.d=t` becomes `{"a": {"b": {"c": {"d": true}}}}`
  * JSON syntax: `?obj={"a":1,"b":2}` becomes `{"a": 1, "b": 2}`

### Query vs. Body parameters

With Instant API, **query and body parameters can be used interchangeably**. If you send both query parameters and body parameters to an endpoint, they will be combined into a single parameter object. GET and DELETE requests only support query parameters.

## That's it!

That covers the basics of building packages on Superuser. If you have any questions, please do not hesitate to jump into our community Discord server at [discord.gg/superuser](https://discord.gg/superuser).


# API keychain specification

A guide to API keychains

## Overview

API keychains are how authentication is managed for Superuser packages. They have three primary responsibilities.

1. Authenticate you via our gateway so you can use Superuser packages
2. Securely store third-party secrets and share them with packages
3. Delegate access to specific packages

## Security considerations

API keychains have the ability to **store and share** third-party secrets with packages. Packages can contain code written by you or third-party developers. This comes with security concerns; notably, how do I prevent unauthorized use of my keys?

At Superuser we take your security seriously and have a three-pillared approach.

1. API keychains can only share secrets with public packages
2. API keychains provision secret access on double opt-in, per package basis
3. Checksum validation: API keychains will automatically expire if the package SHA256 changes

#### 1. API keychains can only share secrets with public packages

Public packages are **open source**, which means you have the ability to manually inspect and verify all code that may have access to your secrets. For example, this [Stripe customers](https://superuser.app/toolkits/@keith/stripe) uses a `STRIPE_SECRET_KEY`. You can manually inspect the code to verify this is only used in the context of instantiating the Stripe package.

#### 2. API keychains provision secret access on double opt-in, per-package basis

You must manually add (a.k.a. install) each package to an API keychain. Every package has its own `instant.package.json` with a section that looks like this:

```json
// instant.package.json
{
  "name": "@user/package",
  "keychain": {
    "required": [
      {
        "name": "STRIPE_SECRET_KEY",
        "description": "Your Stripe secret key"
      }
    ]
  }
}
```

The `keychain.required` array tells us which keys this package wants permission to access. **Keys that are not manually requested in this way can never be shared with packages**. Then, in your keychain, you must manually specify which keys you want to share with the package as well. This is usually done via our Web UI, but the configuration looks like this.

```json
// keychain configuration
{
  "@user/package": {
    "version": "v-20250101",
    "sha256": "SOME-SHA256-HASH-...",
    "keys": ["STRIPE_SECRET_KEY"]
  }
}
```

So in order to share `STRIPE_SECRET_KEY` with `@user/package`, there's a double opt-in mechanism that prevents over-provisioning access:

* The package must explicitly request `STRIPE_SECRET_KEY` in `instant.package.json`
* Your API keychain must explicitly set `"STRIPE_SECRET_KEY"` in its configuration for this package as a key it is willing to share
* **Both of these must be present for a key to be shared**

#### 3. Checksum validation: API keychains will automatically expire if the package SHA256 changes

I am feeling confident — I have (1) verified the code publicly and (2) opted-in to sharing secrets. But what if a malicious package owner or hijacker overwrites a published package unbeknownst to me?

**Every package that gets published to Superuser gets automatically assigned a SHA256 hash based on its contents.** Two packages with identical structure and code will have identical SHA256 hashes.

When you provision package access, you **also** provision a specific SHA256 hash:

```json
// keychain configuration
{
  "@user/package": {
    "version": "v-20250101",
    "sha256": "SOME-SHA256-HASH-...",
    "keys": ["STRIPE_SECRET_KEY"]
  }
}
```

If for any reason the package hash changes from your installation or configuration time, **your keychain will stop working with this package**, preventing unauthorized use or access to your secret keys.

## Accessing API keychain secrets in package code

Once a package has added the necessary `keychain.required` settings in `instant.package.json`:

```json
{
  "name": "@user/package",
  "keychain": {
    "required": [
      {
        "name": "STRIPE_SECRET_KEY",
        "description": "Your Stripe secret key"
      }
    ]
  }
}
```

You can access the `STRIPE_SECRET_KEY` (or whatever parameter you've added) via the [Package specification](/hosted-tools/publishing-tools-via-command-line/package-specification#the-context-argument), like so:

```javascript
export default async function (context) {
  const apiKey = context.keychain.key('STRIPE_SECRET_KEY');
  const stripe = new Stripe(apiKey);
  // go nuts!
}
```

## Using your agent's API keychain

Your agent automatically has an API keychain assigned to it when it is created. It uses this API keychain to manage package access and share secrets with packages. All of this is handled directly via Superuser, and you can read more at [Broken mention](broken://pages/MIDd0424kCtutl5lpVpd).

## Using API keychains for authentication

You can create your own API keychains to use with packages at [superuser.app/dashboard/api-keychains](https://superuser.app/dashboard/api-keychains). If you do not yet have a keychain, you can create one at [superuser.app/dashboard/api-keychains/new](https://superuser.app/dashboard/api-keychains/new) or by clicking **+ Create API keychain** on the keychains page.

<figure><img src="/files/MXUgcxNyVW8ob6zWZ7KG" alt=""><figcaption><p>Create an API keychain</p></figcaption></figure>

Each API keychain created here will have its own **secret key**.

<figure><img src="/files/qDuaujIu1jq8WKSTfsfP" alt=""><figcaption></figcaption></figure>

You can use this secret as a `Bearer` token to access any public package available at [superuser.app/toolkits](https://superuser.app/toolkits).

```sh
curl https://{package}.instant.host/endpoint-name \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {API_KEYCHAIN_SECRET}' \
  --data '{"some":"json"}'
```

For example, the example weather package has a [GET endpoint for retrieving current weather](https://superuser.app/toolkits/@keith/weather/v-20240823/functions/forecast.js?method=GET). You can see instructions for how to use it as a standalone endpoint if you scroll down on the endpoint page:

<figure><img src="/files/crSktqoZ68kzbvTfrLze" alt=""><figcaption><p>Copy endpoint instructions</p></figcaption></figure>

You can see instructions on how to use any specific endpoint via its package page.

## Managing third-party secrets

{% hint style="warning" %}
By default, packages only have access to shared secrets that (1) they request access to and (2) you opt to share. We refer to this as "double opt-in secret sharing"

Adding a secret here **will not** automatically share it with packages. Please see **delegating access to packages** below.
{% endhint %}

Each API keychain can store as many third-party secrets as you'd like. To manage secrets, first visit the API keychains page at [superuser.app/dashboard/api-keychains](https://superuser.app/dashboard/api-keychains) and select your keychain.

<figure><img src="/files/NI5Oi2UmPk83slWmHrW7" alt=""><figcaption><p>Find your keychain</p></figcaption></figure>

This will take you to your API keychain page. Right under the **Secret key** section you'll see a **Shared secrets** section.

<figure><img src="/files/jdMgBHOYVnFyAWewPe4e" alt=""><figcaption><p>Shared secrets</p></figcaption></figure>

You can click **+ Add new key** to add a new shared secret. Then fill out the secret details and save.

<figure><img src="/files/xN5CB95K2sk7bz7pMdff" alt=""><figcaption><p>Set shared secrets</p></figcaption></figure>

That's it! You can add or manage as many keys as you want for each keychain.

## Delegating access to packages

In order to use packages with this keychain, you must **install** each package to the keychain. In practice this means setting a config that looks like this:

```json
{
  "@keith/weather": {
    "version": "v-20250101",
    "sha256": "[some-sha-hash]",
    "permissions": {}
    "keys": []
  }
}
```

* Each property key specifies the package name
* `"version"` specifies which version of the package to use
* `"sha256"` is used to code-lock on a specific deployment (if it gets overwritten, your keychain will prevent access), can be `null` if ignored
* `"permissions"` delegates specific endpoint access
* `"keys"` tells us which keys (shared secrets) you have opted-in to share with this package from your keychain

However, **we manage this configuration for you** via our user interface. Simply visit your keychain page and scroll down to find packages:

<figure><img src="/files/0CxLdNDSXgCf6m0TZdQu" alt=""><figcaption><p>Install packages from the UI</p></figcaption></figure>

You can **Discover** approved packages or add packages that are in **Development**, e.g. created by the community. You can use this to view and manage package configuration and install packages for use with this keychain.

<figure><img src="/files/PYKP6Grw9ebM2QHVhP0j" alt=""><figcaption><p>Manage config or install packages</p></figcaption></figure>

When you **view and manage package configuration** you can set:

* **Security**: manage code-locking with package SHA256
* **Shared secrets**: Which secrets you're sharing from your keychain
* **Endpoint access**: Choose which tools to actually enable with this installation

That's it! Once you've added shared secrets and installed packages, your keychain is good to be used anywhere.


# Managing tools

You can manage your tools directly via their page in the package registry. We'll use the [Superuser: Weather by Open-Meteo](https://superuser.app/org/superuser/toolkits/open-meteo) as an example, available at <https://superuser.app/org/superuser/toolkits/open-meteo>.

## Change name and description

<figure><img src="/files/tfESvlQXikakKE6VXFbk" alt=""><figcaption></figcaption></figure>

To change the name and description of your tool, once it's published just click the edit button next to the name or description.

## View endpoint (and code)

Every tool in a package is just an API endpoint, because Superuser tool packages are just APIs hosted on serverless infrastructure. To view an endpoint and see information about it — like description, parameters, etc. — just click on it in the endpoints list.

{% hint style="warning" %}
Only **open source** packages will show endpoint code. Public and private packages will not show code.
{% endhint %}

<figure><img src="/files/pNCp0WDZ7VedatknFYkd" alt=""><figcaption></figcaption></figure>

Once you've selected the endpoint, you'll be able to see more details:

<figure><img src="/files/cM9OQwTeplum3WAgPDyb" alt=""><figcaption></figcaption></figure>

* Endpoint pathname and description
* Arguments

<figure><img src="/files/hk49uetqwUKmBMOruGx9" alt=""><figcaption></figcaption></figure>

* Endpoint code (if applicable)

<figure><img src="/files/dd15U8qVLcUWQXOFaY28" alt=""><figcaption></figcaption></figure>

* Usage example (how to cURL or execute via Node.js)
* Request parameters (TypeScript or JSON Schema)


# Using tools outside of Superuser

Since every Superuser tool package is just an API server, it's easy to use Superuser tools outside of Superuser. Every package page comes with its own **Servers** section.

<figure><img src="/files/sgHGzGUqRyszGyEleL6D" alt=""><figcaption></figcaption></figure>

* The **REST API Server** is the URL the tool package is available at on the open web
  * This will **always** require authentication (see individual endpoint page for code examples) unless you specifically turn authentication off for your tool
  * Authentication uses [API keychains](/hosted-tools/publishing-tools-via-command-line/api-keychain-specification) which can be managed from your personal or organization dashboard under the **Developer** heading
* The **MCP Server** is the URL that the package is available at as an MCP server for use in other products
  * You can generate an API keychain from the package page directly if you need to


# Tool visibility

You can change tool visibility at any time in the **Danger zone** settings at the **bottom of your tool package page**.

{% hint style="warning" %}
The default visibility is **private.** If you change to **open source**, everybody will be able to see your source code! Be careful.
{% endhint %}

<figure><img src="/files/VfgBAJ7AKagssz71IU7T" alt=""><figcaption></figcaption></figure>

Tool packages come in three flavors;

* `private`&#x20;
  * Can only be executed by API keychains created by your organization
    * Thus can only be used by agents inside your organization, because agents have keychains scoped to the organization they're installed to
    * e.g. Public agent with private tool installed will not be able to execute it
  * Source code is **not visible**
  * Secrets can **not be shared** with these tools
* `public`&#x20;
  * Can be executed by any API keychain
    * And thus, any agent at any time
  * Source code is **not visible**
  * Secrets can **not be shared** with these tools
* `open_source`&#x20;
  * Can be executed by any API keychain
    * And thus, any agent at any time
  * Source code is **visible**
  * Secrets **can be shared** with these tools, as they are inspectable

Select the new visibility you'd like and then confirm, and your package details will be updated.


# Tool authentication

You can change tool authentication at any time in the **Danger zone** settings at the **bottom of your tool package page**. This controls whether or not your tool requires a Superuser API keychain to be executed.

{% hint style="danger" %}
If disabled, **your tool can be executed on the open web by anybody**.
{% endhint %}

{% hint style="warning" %}
Normally, authenticated requests are billed to the requester.

However, you **will be billed directly** for unauthenticated requests to your tools.
{% endhint %}

<figure><img src="/files/KzGbylwUA7Z84wsMUEgo" alt=""><figcaption></figcaption></figure>

Click this switch to OFF to disable authentication. Authentication is enabled by default. You will be asked to confirm before the change is made.


# Archiving tools

You can archive a tool at any time in the **Danger zone** settings at the **bottom of your tool package page**. You can only archive one environment or version at a time, so if you need to remove an entire package with a long version history you will need to repeat this several times.

<figure><img src="/files/DEZpoop2u6lvNbN7nltB" alt=""><figcaption></figcaption></figure>

Click the **\[ Archive ]** button and you will be asked to confirm. Once confirmed, your tool package will be archived.


