# Build a control panel in Retool

Build your own control panel in Retool: buttons that put graphics on air, forms that change their data, and a live view of what's showing.

> **Before you start.** You need an output with a composition, and an [API key](https://docs.airframe.tv/api/authentication) that controls it. Owners and admins make keys in **Organisation settings › API keys**.

## Add Airframe as a resource

A Retool resource stores the API key on Retool's servers. Your key never reaches the browsers of the people using the app.

1. In Retool, open **Resources** and choose **Create new › Resource**, then **REST API**.
2. Name it `Airframe`.
3. Set **Base URL** to `https://api.airframe.tv/v1`.
4. Under **Headers**, add `Authorization` with the value `Bearer afk_…`, using your key.
5. Choose **Create resource**.

## Send a command

Make a query that puts a lower third on air with the name typed into a form.

1. In your app, add a **Text Input** named `nameInput` and a **Button** labelled `Take`.
2. In the code panel, add a **Resource query** named `takeLowerThird` that uses the `Airframe` resource.
3. Set the method to **POST**, the path to `outputs/OUTPUT_ID/commands`, and the body type to **JSON**.
4. Set the body to the command, with the name from the input:

```json
{
  "commands": [
    { "command": "in", "component": "lower-third", "data": { "name": "{{ nameInput.value }}" } }
  ]
}
```

5. On the button, add an event handler: on **Click**, **Control query** `takeLowerThird`.

Replace `OUTPUT_ID` with the output's id, the last part of its browser source URL, and `lower-third` with your component's id. The [commands](https://docs.airframe.tv/api/commands) page lists every command you can send.

## Clear the output

Make a second query, `clearAll`, the same way, with this body, and run it from a **Clear** button:

```json
{ "commands": [{ "command": "out", "scope": "root" }] }
```

## Show what's on air

1. Add a query named `outputState` that sends **GET** to `outputs/OUTPUT_ID/state`.
2. Set it to run when the app loads, and in its advanced settings, to run periodically, every 2 seconds or so.
3. Show its data in components, for example a **Table** with data `{{ Object.entries(outputState.data.onAir) }}`, or text reading `{{ outputState.data.data.global.homeScore }}`.
4. Add a success handler to `takeLowerThird` and `clearAll` that triggers `outputState`, so the view updates straight after a take.

[Get the output's state](https://docs.airframe.tv/api/state) describes everything it returns.

## Tips

- **Batch related changes.** A score change and a goal animation can go in one request, so they land together: an `update` then an `invoke`.
- **Mind the rate limit.** Keep polling to every second or two. Each key can send 200 command batches every 10 seconds; see [Errors and rate limits](https://docs.airframe.tv/api/errors).
- **Show errors.** A failed command answers `422` with the reason and which command failed. Add a failure handler that shows a notification with `{{ takeLowerThird.error }}` so operators know when a take didn't happen.
