Perch
Lua APIperch.http

perch.http

Pinned-host async fetch — the whole third-party-data story, with its caps and callback contract.

2 min readUpdated Jul 21, 2026

perch.http.get exists only if your manifest declares at least one net.http:<host> scope — and reaches only those hosts. It is the entire network surface: no sockets, no POST, no websockets, by design.

"scopes": ["net.http:api.open-meteo.com", "timer"]

perch.http.get(url, callback, opts?)

perch.http.get("https://api.open-meteo.com/v1/forecast?latitude=35.7&longitude=139.7&current=temperature_2m",
  function(status, body)
    if status ~= 200 then return end
    local data = perch.json.decode(body)
    if not data then return end
    host_ref.push_data("mywx", { temp = math.floor(data.current.temperature_2m + 0.5) .. "°" })
  end)

Fully async: a C++ worker performs the fetch off-thread; your callback lands on the main thread later. Lua never blocks, and no budget is spent waiting.

The callback

callback(status, body):

statusMeaning
200599the HTTP status; body = response (capped)
-1refused or failed: body = "scope" (unpinned host / not https), "rate" (hourly cap), "unavailable", or empty when the request went out but the network failed (no response)

opts

{ ua = "<string>" } overrides the default perch-mod/<id> user agent — some public APIs demand a browser UA. The override is manifest-visible behavior; use it because an API requires it, not to disguise traffic.

The caps

CapValue
schemehttps:// only
hostexact match against a pinned scope — no subdomain wildcards
rate30 requests per hour per mod, rolling window
body1 MB — larger responses are truncated at the cap

A 2-minute poll is the entire budget — one retry tips it. Design for 5+ minutes with on_page_visibility gating: poll fast only while your page is actually visible.

local visible = false
function M.on_page_visibility(host, widget, v)
  visible = v
  host.timer("poll", (v and 5 or 30) * 60 * 1000)
end

Failure is normal

Networks flake. The pattern every builtin uses:

  1. Keep the last good payload in a file-level local (or perch.storage if it should survive restarts).
  2. On failure, keep showing it — maybe with a quiet staleness hint.
  3. Never set_presence(false) because one fetch failed — a configured slot showing yesterday’s data beats a hole.
esc
Type to search
navigate open