Docs menu

Reference

Manifest reference

Every table and key in slowapp.toml for contract 3, and the rules the checker enforces across keys.

slowapp.toml sits at the root of an app’s repository and is copied into the image at /app/slowapp.toml. It names the app, declares its grants, jobs and feeds, and lists the examples used for test renders.

  • Check it with python3 -m slowapp_sdk validate (Python SDK reference).
  • The schema is sdk/slowapp_sdk/schema/slowapp.schema.json in slowapp-base. The rules a schema can’t express are listed below.
  • Unknown keys are errors in the checker, so a typo can’t silently drop a grant. The hub ignores unknown keys, so a newer manifest still loads on an older hub.

Example: Reminders

From slowapp-reminders: questions on the screen, no network.

[app]
id = "reminders"
title = "Reminders"
contract = 3
image = "ghcr.io/mike-anderson/slowapp-reminders"
memory = "48m"
tmp = "8m"
timeout_s = 30
network = "none"
prompts = true
imports = ["legacy:reminders"]   # P4 transitional: reminders.json, owned by the legacy page

[[job]]
name = "render"
expected_s = 3
daily = "00:01"

[[feed]]
name = "prompts"
size = "slot"

[[example]]
name = "due"
caption = "6 Oct 2026 10:00: watering due, NexGuard re-asks at 18:00, the vet at 15:00"
fixture = "fixtures/two-due"
now = "2026-10-06T10:00:00-07:00"
feeds = { prompts = "452x444.k4" }

The Weather tutorial shows a networked app’s manifest, with storage and three feeds.

[app]

KeyTypeMeaning
id[a-z][a-z0-9-]{1,23}The app’s name everywhere: in the layout (<id>/<feed>), on the hub, in paths. The repository is slowapp-<id> by convention.
titletext, up to 40 charactersShown on the hub’s app card.
contract3The contract version. A hub refuses any other.
imagetextThe image’s registry path, without a tag. The hub pins the exact digest when the app is installed.
memorysize (48m, 1g)Hard memory limit. tmp and cache count against it.
tmpsizeSize of /tmp, a scratch folder in memory.
timeout_s1–3600The job is stopped after this many seconds (exit 137).
networknone or egressegress allows outgoing connections, to the internet and to your home network, but never to the hub’s own machine.
secretslist of namesSecrets the app needs, as files at /run/secrets/<name>. If one is missing the job ends with “needs config” (exit 5).
optional_secretslist of namesSecrets given to the app if they are set.
data{ max_mb = N }A private /data folder that survives between runs. The hub pauses the app if it grows past max_mb.
cache{ tmpfs_mb = N }A /cache folder in memory, emptied when the container stops.
promptstrue or falseThe questions grant: the app may put questions on the screen. Needs a feed named prompts.
exports, importslistsTransitional, for apps moving out of the older system. New apps don’t use them.

[settings]

Each key ([a-z][a-z0-9_]{0,31}) is a setting. The hub shows it in a form and gives the values to the app in /in/settings.json; in Python, ctx.settings fills in the defaults. See Settings, schedules and pages.

FieldMeaning
typestring, number, integer, boolean or choice
label, helpText for the form
optionalMay be left empty
defaultDefault value
min, maxBounds for numbers
choicesThe allowed values of a choice
[settings]
tz_override = { type = "string", optional = true }
lookahead_days = { type = "number", default = 14, min = 1, max = 60 }

[ui]

Only for apps with a web page. See Settings, schedules and pages.

KeyMeaning
idle_s10–3600. Stop the page’s container after this long with no viewer and no job.
start_timeout_s1–120. How long serve may take to start answering.

[events]

[events]
accept = ["answer"]

The kinds of answer payload (emit) this app receives. Needs a job named event. See Questions and answers.

[[job]]

KeyMeaning
name[a-z][a-z0-9_-]*. render draws the feeds and is run when a screen needs cards; event receives answers.
everyA schedule such as 15m or 1h, at least 5 minutes.
dailyHH:MM, local time. Not together with every.
jitter_sA random delay added to a schedule.
inputsself:<export> or <app>:<export>: skip the job while these inputs haven’t changed.
expected_sHow long the job usually takes, used until the hub has measured it.

Every job can also be run by hand.

[[feed]]

KeyMeaning
nameThe feed name; the layout refers to it as <app>/<name>.
size"slot": the size of whatever slot shows the feed, passed to the app on each run. "WxH" fixes the size.

[[example]]

A test render. The checking tools and CI render every example and check the output.

KeyMeaning
nameExample name; previews are named <app>-<example>-<feed>-<card>.png.
fixturefixtures/<folder>: the test data, given to the app as a read-only /data.
nowThe fixed time, RFC 3339 with an offset.
feedsWhich feeds to draw, at which surface: { prompts = "452x444.k4" }.
jobThe job to run. Default render.
settingsSettings for this example.
tzTime zone for this example.
captionA caption for the preview.

[ci]

Read only by CI; the hub ignores it. depends is kept for compatibility.

Cross-field rules

The checker enforces:

  • Job, feed and example names are unique.
  • An app with feeds has a render job; [events] accept needs an event job.
  • prompts = true if and only if there is a feed named prompts.
  • A secret is either required or optional, not both.
  • every and daily exclude each other; jitter_s needs one of them; every is at least 5 minutes.
  • tmp plus cache.tmpfs_mb is less than memory.
  • Setting defaults have the right type and sit within min, max and choices.
  • Each example’s fixture folder exists, its job and feeds are declared, and a fixed-size feed matches the example’s surface.

It warns, without failing, about a slot surface other than 452 or 456 × 444, a feed with no example, and a required setting with no default.

Updates, now and then

A short note when there is a new board, a new slowapp, or kit news. Rarely more than one a month.