Skip to main content

HTTP requests (http.*)

Every app.star gets a global http struct for fetching live data. You call it, but the GDN host makes the actual network request; your Starlark code stays fully sandboxed and only ever sees the response data. The host adds a hard timeout and response caching, so a slow or flaky endpoint can't hang a render and repeated renders don't overload an API.

def main(c, ctx):
resp = http.get("https://api.coingecko.com/api/v3/simple/price",
params = {"ids": "bitcoin", "vs_currencies": "usd"})
if resp["status_code"] == 200 and resp["json"] != None:
c.text("$" + fmt.commas(int(resp["json"]["bitcoin"]["usd"])),
4, 10, font = "8x12", color = "white")
else:
c.text("NO DATA", 4, 12, font = "5x7", color = "red")

http.get(url, headers = {}, params = {}, ttl_seconds = 300)

ArgumentTypeWhat it does
urlstringThe endpoint. Must start with http:// or https://.
headersdictRequest headers, e.g. {"x-api-key": key}. Optional.
paramsdictQuery parameters, appended to the URL for you. Optional.
ttl_secondsintHow long the response may be served from cache. Default 300 (5 minutes). Pass 0 to always refetch.

The response

http.get returns a dict:

KeyTypeMeaning
status_codeintThe HTTP status (200, 404, ...), or 0 if the request never completed (timeout, DNS failure, connection refused).
bodystringThe raw response text. Empty when status_code is 0.
jsonanyThe body decoded as JSON, or None if the body isn't valid JSON.
errorstringNone normally; a short message when status_code is 0.

Check the response before you draw.

resp = http.get(url)
if resp["status_code"] != 200 or resp["json"] == None:
c.text("NO DATA", c.width // 2, 12, font = "5x7", color = "red", align = "center")
return
data = resp["json"] # already decoded, index it directly

Network trouble never crashes your app. You always get a response back. A status_code of 0 (with resp["error"] explaining why) means the request couldn't complete, and any non-200 status means the server returned an error. Draw a fallback and return.

API keys

Declare the key as an input (see Inputs & configuration), read it from ctx.inputs, and pass it wherever your provider expects it, a header or a query parameter:

key = ctx.inputs.get("api_key", "")
resp = http.get("https://api.example.com/v1/data",
headers = {"x-api-key": key}, # header style
params = {"appid": key}) # or query-param style
Treat API keys as compromised

Glance stores the keys people enter into your app's inputs on our servers, encrypted, and only the Glance team can decrypt them to run your app. Even so, treat any API key as if it could leak:

  • Rotate keys regularly and assume a key could be exposed. Decryption by a bad actor is never impossible, however well a key is secured.
  • Glance is not responsible for leaked keys, or for any usage or charges on a third-party API. We secure stored keys as best we can, but nothing is fully safe in a world of capable attackers and AI tooling.
  • Be careful with paid keys, both when you build an app and when you prompt users to enter their own. A leaked key can run up real charges.

Caching

Responses are cached by (url, params, headers) for ttl_seconds. Within that window, repeated renders reuse the stored body instead of refetching, which also works offline. Only successful (2xx) responses are cached, so a failed response is never served from cache. Match ttl_seconds to your data: 300 for prices, 3600 for weather, 0 only if every render needs a fresh fetch.

Timeouts and limits

  • Each request has a hard 10 second timeout. A slow endpoint comes back as status_code: 0 instead of hanging the panel.
  • An app may make at most 8 uncached requests per render. Cache hits don't count. Exceeding it stops the render with a clear error.
  • Response bodies are capped at 1 MB. Panel apps need kilobytes; if you're fetching more, find a smaller endpoint.
  • Only GET is available. Panel apps read data; they don't post it.

See it used end to end

The Bitcoin price example is a complete app, manifest, logo, fetch, parse, draw, built around one http.get call.