at.Applied Theology DOCUMENTATION Back to site
Start here

Build a new app

An ordinary platform app is a folder, a manifest, and application code. The platform discovers it and mounts its URL without a new nginx rule, port, or service.

1. Scaffold

at new my_app --name "My App"

This creates apps/my_app/ with a backend and React frontend. Use --no-web for an API-only app. New apps default to administrator access while you build them.

apps/my_app/
  app.toml       name, URL, access, capability requirements
  api.py         AppKit instance and HTTP routes
  jobs.py        optional background handlers
  README.md      app-specific documentation and notes
  web/           frontend source and built assets

2. Declare the requirements

name = "My App"
path = "/my-app"
access = "authenticated"
requires = ["identity", "database", "files", "jobs"]
required_env = []
required_secrets = []

Use admin for private tools, authenticated for signed-in humans, and public when the app deliberately supports anonymous visitors. A public route that starts paid AI work must still have appropriate authorization and a budget.

Capabilities supported by preflight are identity, database, files, jobs, ai, and kv. Declare only the ones you use. kv requires a separately provisioned DynamoDB table.

3. Use the shared abstractions

from fastapi import Request
from pydantic import BaseModel, Field
from at import AppKit

kit = AppKit(__name__)
router = kit.router

class Note(BaseModel):
    text: str = Field(min_length=1, max_length=10000)

@router.post("/api/notes")
async def save_note(body: Note, request: Request):
    user = await kit.auth.require(request)
    store = kit.store.for_owner(user.id)
    note_id = await store.put("notes", body.model_dump())
    return {"id": note_id}

@router.get("/api/notes")
async def list_notes(request: Request):
    user = await kit.auth.require(request)
    return await kit.store.for_owner(user.id).list("notes", limit=50)

These routes become /my-app/api/notes. The owner comes from verified identity, not a request body. For uploads, call kit.enable_files() once to install the shared owner-scoped file endpoints.

4. Build the interface

The default frontend uses React, TypeScript, Vite, Tailwind, and @at/ui. Plain HTML and static multi-page apps are also supported.

import { api, Button, Card, Page } from "@at/ui";

// Resolves to this app's API, including its configured URL prefix.
const notes = await api<{ id: string; text: string }[]>("notes");

See UI & live updates for the shared components and path conventions.

5. Check and deploy

at doctor my_app
at build my_app
at doctor --live
at deploy

If you added SQL tables, generate and review a migration, test it on an isolated database, and apply it with operator credentials before deploying. Runtime database users cannot perform schema changes.

Deploy checks requirements, schema state, dependency readiness, and every app's HTTP page. Open the real URL and exercise the flow as well. A successful health check is not a substitute for checking authorization and user behavior.

6. Leave a trail

Add an app README.md, list the app in the repository catalog, and commit the app and documentation together. The app directory discovers the new manifest and links its notes on the next deployment.

Use the private authoring guide for the full repository workflow.