# Apps

An app is a service a person connected, with the tools a worker may use in it and what it may do there.

An app is a service that a person connected to Little Worker, such as a mail, calendar or tracker service. Once it is connected, a worker can use the app's tools: read a calendar, file an issue, send a note. An app is an MCP server in Claude Managed Agents.

The app itself belongs to the person. They sign in to it and set how much workers may do in it, in the app. The API lets you list the apps, install one by its address, and choose which apps a worker uses. This page explains what an app holds, what Read and Act mean, how a worker gets an app, and how to reach an app on a private network.

## What an app holds

`GET /apps` lists the apps a person connected. It shows nothing of an app's sign-in.

| Field | What it is |
| --- | --- |
| `id` | The app's id: `app_` and a number, such as `app_12`. |
| `name` | The app's name. |
| `url` | The address of the app's server, without any query, which may carry a key. It is the address a worker's `apps` entry names. |
| `connected` | `true` when the person signed in to the app. |
| `read` | `on` or `off`. |
| `act` | `allow`, `ask` or `off`. |

`POST /apps` with a `name` (1 to 80 characters) and a `url` (up to 2,048 characters) installs an app by its address and answers it. The address must pass the same rule Little Worker applies whenever it calls an app: HTTPS, a public address, and no credentials in it. An address that is already installed answers that app. Signing in to an app, and changing its Read and Act, are the person's, in the app.

## Read and Act

Every app has two levels. They are the workspace's ceiling for that app.

| Level | Values | What they do |
| --- | --- | --- |
| `read` | `on` or `off` | Whether the app's tools that only read may run. |
| `act` | `allow`, `ask` or `off` | What happens to a tool that changes something. `allow` runs it, `ask` waits for an Allow, and `off` refuses it. |

A worker's settings can narrow these levels but never widen them. And some steps ask whatever the levels say: a step that sends, pays, posts or deletes always asks. See [Approvals](/concepts/approvals).

## How a worker gets an app

A worker uses only the apps it names. A new app reaches no worker until a person chooses the worker for it on the Apps page, or you name the app on the worker.

A worker names its apps in `apps`, up to 20. Each is `{ "type": "url", "name", "url" }`, and each `name` is used once. Each `url` must match an app installed in the workspace, or the answer is `400` naming the app.

Each app in `apps` needs one entry in the worker's `tools`: its app toolset, `{ "type": "app_toolset", "app_name", "default_config", "configs" }`. An app without a toolset, or a toolset without an app, is `400` naming the app, and an app has at most one toolset.

- `app_name` is the `name` of the app in `apps`.
- `default_config` covers every tool of the app that `configs` does not list. It is `{ "enabled", "permission_policy": { "type" } }`. Left out, the tools are on and `always_ask`.
- `configs` lists single tools by their own `name`, each with `enabled` and a `permission_policy`.

`permission_policy.type` is one of three values:

| `permission_policy.type` | What it does |
| --- | --- |
| `always_allow` | Runs the step. |
| `always_ask` | Waits for an Allow, even for a tool that only reads. |
| `auto` | Lets a tool that only reads run, and sends every other step to a safety check. The check runs what it allows, refuses what it blocks, and asks when it is unsure, slow (3 seconds) or unavailable. When the app's `act` is `ask`, `auto` asks too. |

When you add an app to a worker, send its toolset in the same call. A change to `tools` replaces the whole list, so send the entries you want to keep as well. Both lists are part of the worker's version, and a task keeps the apps and toolsets of the worker version it started with. A change reaches the next task, not the one at work.

To give one task different apps without changing the worker, give the task a `worker` of `{ "type": "worker_with_overrides", ... }` with its own `apps` and `tools`, or change them with `POST /tasks/{task_id}` while the task is `idle`. The worker does not change.

## What you see when a worker uses an app

A call to an app is a pair of events in the task:

- `worker.mcp_tool_use` has the `app_name`, the app's own tool `name`, and the `input`. It carries `evaluated_permission` and `evaluation` once something has decided the step.
- `worker.mcp_tool_result` has the app's answer. `mcp_tool_use_id` names the call, and `is_error` says whether it failed.

Two task errors concern apps. `app_connection_failed_error` means the app could not be reached, and `app_authentication_failed_error` means it refused the sign-in. Both name the app in `app_name` and leave the task working.

Each finished run has a receipt that lists the app actions decided in it: the tool, whether it was `allowed` or `denied`, and whether a setting or a person decided it (`decided_by`). What the worker sent to the app never appears. See the [Tasks reference](/api/tasks#read-what-a-task-cost).

Apps are called by Little Worker, never from a computer, so a computer's networking cannot turn them off. See [Computers](/concepts/computers).

An app's sign-in is kept as a password, in a password set. See [Password sets](/concepts/password-sets).

## Apps on a private network

Some apps live on a private network and cannot be reached from the internet. A tunnel is a way in to them. Nothing opens an inbound port.

- **Make a tunnel** with `POST /tunnels`, with an optional `display_name`. It gets an assigned `domain`.
- **Run the connector.** `POST /tunnels/{tunnel_id}/reveal_token` answers the connector token that Cloudflare's `cloudflared` runs with. Run Little Worker's tunnel proxy beside `cloudflared` on the private network. `POST /tunnels/{tunnel_id}/rotate_token` makes a new token.
- **Give it a certificate.** A tunnel takes calls only while it holds a CA certificate. `POST /tunnels/{tunnel_id}/certificates` takes a `ca_certificate_pem` of up to 8,192 bytes. A tunnel holds two at a time, and `POST .../certificates/{certificate_id}/archive` archives one. Little Worker trusts only the CAs registered on the tunnel.
- **Install the app** at an address under the tunnel's `domain`.

An app whose address is under the tunnel's `domain` is reached through the tunnel and never over the public internet. Only the tasks of the workspace that owns the tunnel can reach it. `GET /tunnels` lists tunnels, `GET /tunnels/{tunnel_id}` reads one, and `POST /tunnels/{tunnel_id}/archive` archives one. Add `include_archived=true` to a list to see archived ones.

## Limits

| What | Limit |
| --- | --- |
| Apps on one worker | 20, each name used once |
| App name when installing | 1 to 80 characters |
| App address | 2,048 characters |
| Toolsets for one app | 1 |
| CA certificates on a tunnel | 2 at a time, each up to 8,192 bytes |
| Automatic check | 3 seconds, then it asks |

## Next steps

- [Approvals](/concepts/approvals): how an app step that asks reaches a person, and who may answer.
- [Password sets](/concepts/password-sets): where an app's sign-in is kept.
- [Runs and events](/concepts/runs-and-events): the `worker.mcp_tool_use` and `worker.mcp_tool_result` events in order.
