> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capsule.new/llms.txt
> Use this file to discover all available pages before exploring further.

# App API

> `cpsl.App` and the core app-building API.

`cpsl.App` is the root object for a Capsule application.

The shortest useful way to think about it is this: `App` is both the place where you describe the product and the object Capsule reads when it serves or deploys that product. Pages, collections, integrations, and deploy knobs all live on the same object for that reason.

## Worked example

This is the kind of `App` definition a real product grows into:

```python theme={null}
import cpsl
import cpsl.ui as ui
from openai import AsyncOpenAI

client = AsyncOpenAI()

app = cpsl.App(
    name="support-ops",
    image=cpsl.Image(python_packages=["openai"]),
    channels=[cpsl.Channel("support-telegram")],
    keep_warm_seconds=30,
    cpu=0.5,
    memory=1024,
    secrets=["OPENAI_API_KEY"],
    filesystems={"/data": cpsl.FileSystem("support-ops")},
    price=1500,
    pricing_type="monthly",
)

app.add_integration(
    cpsl.Gmail(
        client_id=cpsl.Secret.from_name("GOOGLE_OAUTH_CLIENT_ID"),
        client_secret=cpsl.Secret.from_name("GOOGLE_OAUTH_CLIENT_SECRET"),
        scopes=["https://www.googleapis.com/auth/gmail.readonly"],
    )
)

threads = app.collection(
    "threads",
    columns=["subject", "status", "priority", "thread_id"],
    scope="owner",
    sortable=True,
    filterable=True,
)

app.setting("auto_sync", scope="owner", type=bool, default=True)


@app.data("mailbox_metrics", access="authenticated")
async def mailbox_metrics():
    rows = await threads.find(limit=500)
    return {"total_threads": len(rows)}


@app.page("Overview", icon="layout-dashboard")
def overview():
    return ui.Page([ui.Metric("Threads", data="mailbox_metrics", field="total_threads")])


@app.task()
async def sync_threads(session: cpsl.Session | None = None):
    if session:
        await session.notify("Syncing mailbox state...")
    await threads.insert_one(
        {
            "subject": "Renewal review",
            "status": "needs-review",
            "priority": "high",
            "thread_id": "thread_123",
        }
    )


@app.message()
async def handle(session: cpsl.Session, msg: cpsl.Message):
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "You are a support operations copilot."},
            *session.chat_messages(msg),
        ],
    )
    await session.reply(response.choices[0].message.content or "No response.")
```

You do not need everything at once, but this example shows why `App` is the center of Capsule. Runtime, pages, state, integrations, tasks, channels, and pricing all hang off the same object.

## Constructor

```python theme={null}
app = cpsl.App(
    name="my-app",
    image=cpsl.Image(python_packages=["openai"]),
    channels=[cpsl.Channel("my-telegram-bot")],
    keep_warm_seconds=30,
    cpu=0.25,
    memory=512,
    secrets=["OPENAI_API_KEY"],
    filesystems={"/data": cpsl.FileSystem("reports")},
    price=500,
    pricing_type="monthly",
)
```

You do not need all of these fields on every app. A small app often starts as just:

```python theme={null}
app = cpsl.App(
    name="hello",
    image=cpsl.Image(),
)
```

### Constructor arguments

| Argument            | Meaning                                               |
| ------------------- | ----------------------------------------------------- |
| `name`              | App name used for deploys and lookups                 |
| `image`             | Runtime image spec. Required for functional apps      |
| `channels`          | External transport declarations. Web chat is implicit |
| `keep_warm_seconds` | Keep the runtime warm after activity                  |
| `cpu`               | Number of vCPUs allocated to the sandbox              |
| `memory`            | MiB of RAM allocated to the sandbox                   |
| `secrets`           | Workspace secret names to inject into the runtime     |
| `filesystems`       | Mount-path to `FileSystem` mapping                    |
| `price`             | Paid app access price in cents                        |
| `pricing_type`      | `"one_time"` or `"monthly"`                           |

## App-building methods

Most of the `App` API is declarative. You call these while the module is imported, and Capsule remembers the resulting configuration. The main question is usually not "how do I call this?" but "which part of the app should own this concern?"

### `app.collection(...)`

Declare a persistent collection and get back a `CollectionRef` you can use from handlers.

```python theme={null}
contacts = app.collection(
    "contacts",
    columns=[cpsl.Column("email", type="email")],
    scope="app",
    sortable=True,
    filterable=True,
    paginate=20,
)
```

This is usually the first thing to reach for when the app needs real persistent records.

### `app.setting(...)`

Declare a scoped setting that can be bound to UI widgets or read from Python.

```python theme={null}
app.setting("daily_goal", scope="user", type=int, default=3)
```

Settings are better for configuration than for records. Think "user preference", not "business object".

### `app.theme(...)`

Configure colors, fonts, tagline, logo, and preset.

```python theme={null}
app.theme(preset="midnight", accent="#A78BFA", font_sans="DM Sans, sans-serif")
```

In practice, most apps call this once near the top of the file and rarely touch it again.

### `app.add_integration(...)`

Declare a user-facing integration.

```python theme={null}
app.add_integration(cpsl.AWS())
app.add_integration(
    cpsl.GitHub(
        client_id=cpsl.Secret.from_name("GITHUB_CLIENT_ID"),
        client_secret=cpsl.Secret.from_name("GITHUB_CLIENT_SECRET"),
        scopes=["repo"],
    )
)
```

If the integration belongs to the end user, declare it here. If the credential belongs to the app itself, that is usually a workspace secret instead.

### `app.add_page(...)`

Register a React/TSX page.

```python theme={null}
app.add_page(
    "Dashboard",
    icon="layout-dashboard",
    component="pages/dashboard.tsx",
    packages=["lightweight-charts@4.2.0"],
    access="authenticated",
)
```

Use this when the page needs richer interaction or external frontend libraries.

### `app.page(...)`

Decorator for Python DSL pages. The function must return `cpsl.ui.Page`.

```python theme={null}
@app.page("Overview", icon="chart-bar")
def overview():
    return cpsl.ui.Page([...])
```

Use this when the page is mostly a composition of metrics, tables, charts, and settings controls.

### `app.workflow(...)`

Register a named workflow surface.

```python theme={null}
wf = app.workflow(
    "Research Company",
    icon="search",
    description="Collect context and draft a brief.",
    scope="user",
)
```

Use workflows when users should start a named flow from the sidebar and continue inside a session-backed chat. See [Workflows](/reference/workflows) for launcher UI, start handlers, actions, and message handlers.

### `app.data(...)`

Register a named JSON data source.

```python theme={null}
@app.data("stats", access="authenticated")
def get_stats():
    return {"open_tasks": 12}
```

This is the cleanest way to expose computed data to both DSL pages and React pages.

## Functional-only methods

These are only valid when the app was created with `image=...` on the constructor:

* `app.boot()`
* `app.shutdown()`
* `app.enter()`
* `app.exit()`
* `app.message()`
* `app.schedule(cron)`
* `app.endpoint(...)`
* `app.asgi(...)`
* `app.task(...)`

Here is a small functional app that uses several of them together:

```python theme={null}
import cpsl
from openai import AsyncOpenAI

client = AsyncOpenAI()

app = cpsl.App(
    name="ops-bot",
    image=cpsl.Image(python_packages=["openai"]),
    secrets=["OPENAI_API_KEY"],
)


@app.boot()
async def on_boot():
    print("booted")


@app.message()
async def handle(session: cpsl.Session, msg: cpsl.Message):
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "You are a concise operations copilot."},
            *session.chat_messages(msg),
        ],
    )
    await session.reply(response.choices[0].message.content or "No response.")


@app.task()
async def sync_index():
    ...
```

## Class-based apps

For class-based apps, use `@app.cls(...)` and the global decorators from `cpsl`:

```python theme={null}
from openai import AsyncOpenAI

app = cpsl.App(name="my-app")


@app.cls(image=cpsl.Image(python_packages=["openai"]), secrets=["OPENAI_API_KEY"])
class MyApp:
    @cpsl.boot()
    def setup(self):
        self.client = AsyncOpenAI()

    @cpsl.message()
    async def handle(self, session: cpsl.Session, msg: cpsl.Message):
        response = await self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "You are a concise Capsule assistant."},
                *session.chat_messages(msg),
            ],
        )
        await session.reply(response.choices[0].message.content or "No response.")
```

### `@app.cls(...)` arguments

| Argument            | Meaning                                  |
| ------------------- | ---------------------------------------- |
| `image`             | Runtime image spec                       |
| `price`             | Paid app access price in cents           |
| `pricing_type`      | `"one_time"` or `"monthly"`              |
| `channels`          | External channels                        |
| `keep_warm_seconds` | Warm runtime window                      |
| `cpu`               | Number of vCPUs allocated to the sandbox |
| `memory`            | MiB of RAM allocated to the sandbox      |
| `secrets`           | Secret names to inject                   |
| `filesystems`       | Mount-path to `FileSystem` mapping       |

## `app.settings`

Every `App` has a settings accessor:

* `await app.settings.get(key)`
* `await app.settings.set(key, value)`
* `await app.settings.get_all(keys=None)`

Example:

```python theme={null}
goal = await app.settings.get("daily_goal")
await app.settings.set("daily_goal", goal + 1)
```

## See also

* [Decorators Reference](/reference/decorators)
* [Collections And Settings Reference](/reference/collections-and-settings)
* [Workflows Reference](/reference/workflows)
* [Image, Theme, And Deployment Reference](/reference/image-theme-and-deployment)
* [Pricing And Payments](/features/pricing-and-payments)
