Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

uFns

Small integration functions for ToughTongue AI — mock user details, weather + headlines lookups, webhook capture, and a single tabbed admin UI (Data / Debug / Activity / Info).

Pair with uAgents for heavier per-customer agent APIs behind header secrets.

ValRole
uFnsLightweight integration fns (/get-user-details, /webhook, …)
uAgentsCustomer-scoped agent fns at /fns/<customer>/<fn>

Deployed to the ttai org on Val Town (vt create … --org-name ttai).

Auth

Two independent surfaces:

  • Fn calls — one global API key, sent as Authorization: Bearer <key> (or X-API-Key: <key>) on every request. The key is auto-minted on first use, viewable and rotatable on the Data tab. There is no per-customer scoping (that's what uAgents is for) — one key, checked in constant time, guards every fn.
  • Admin UI (/) — Val Town sign-in, gated to an allow-list, same pattern as uAgents. Set the ADMIN_USERS env var (Val Town sidebar) to a comma-separated list of Val Town usernames (case-insensitive). Sign-in uses std/oauth; see backend/auth.ts.

Admin UI

A single page at / with five hash-routed tabs (/#data, /#debug, /#integration, /#activity, /#info):

TabWhat
DataMock user fields (returned by /get-user-details) + the global API key
DebugPick a fn, edit its JSON body, and fire a live call from the browser — see the resolved URL, Authorization header, and an equivalent curl update live
IntegrationPick a fn and get its endpoint URL, auth header, description, and parameters JSON Schema — ready to paste into a ToughTongue AI tool/function definition
ActivityFn catalog + a live request/webhook log
InfoRole, auth model, quick start, pairing with uAgents

Fn endpoints

All POST-only, all require the API key:

PathPurpose
/get-user-detailsReturns the mock user from SQLite
/get-informationWeather + headlines, padded to ~3s
/get-information-slowWeather + headlines, padded to ~20s
/webhookCaptures a POST body for the Activity log — a regular fn like the rest, not a catch-all

/get-information* accept an optional JSON body {"location": "Tokyo"}; without one they fall back to the mock user's location (Data tab, default Paris).

Ready-to-run curl examples with the real deployed base URL filled in live on the Activity tab (empty-state) and the Info tab (quick start) — no guessing your val's hostname.

Weather + headlines API options

Both fns call free, keyless public APIs — no signup, no rate-limit surprises for a dev tool:

  • Weather — Open-Meteo (chosen). Free forecast + geocoding APIs, no API key, no rate limit for reasonable use. Alternatives considered: wttr.in (free, keyless, but plain-text/ANSI-first — JSON is an afterthought) and OpenWeatherMap (richer data, but requires a free-tier API key — adds a required env var for a mock endpoint).
  • Headlines — Hacker News (Firebase) API (chosen). Free, keyless, and consistently fast (topstories.json + a parallel item/<id>.json fetch per story — measured ~0.3s per call). Headlines skew tech/HN, not general news. Alternatives considered: GDELT DOC 2.0 (free, keyless, broader real-world news coverage via full-text search — but measured ~13s per query from this repo's dev network, too slow/unpredictable for a 6–20s budget); NewsAPI.org / GNews / currentsapi (broader coverage and categories, but all require a free-tier API key).

File layout

backend/
  api.ts              HttpError, readJson/readText, dispatch (named api.ts, not
                       http.ts — Val Town infers a val's type from its filename;
                       only index.http.ts should be an HTTP val)
  auth.ts              OAuth admin gate + global API key check
  config.ts            mock-user fields, FN_SPECS (fn catalog + Debug samples)
  db.ts                 SQLite: settings, request log, webhook log
  settings-store.ts     mock-user settings + the global API key
  information.ts        weather + headlines fetch/pad logic
  fns/                  one file per fn handler
  pages/                view-model assembly for SSR (admin.ts, activity.ts)
frontend/
  layout.tsx            Shell, Tab, Panel, Picker, ReadOnlyField, UserBadge
  admin.tsx             composes Data/Debug/Activity/Info into the tabbed page
  data.tsx, debug.tsx, activity.tsx, info.tsx    one tab each
  auth.tsx              signed-out / access-denied pages
  styles/               modular CSS layers (Next.js-style)
    tokens.ts           design tokens + semantic M3 aliases (palette is runtime-generated)
    base.ts             document layout + typography scale
    components.ts       cards, tabs, key row, fields/buttons/tables, debug box
    index.ts            composes layers → GLOBAL_CSS

Push

From this directory:

vt create uFns . --org-name ttai --upload-if-exists --no-editor-files vt push # ship changes

val.yml is not read by vt yet — it documents the val contract for a future repo wrapper over vt-cli.

Add a function

  1. Write a thin handler in backend/fns/<name>.ts — throw HttpError for client errors; body parsing via readJson / readText from backend/api.ts.
  2. Register it in ROUTES inside index.http.ts, wrapped in withAuth(...) so it requires the global API key (dispatch wraps error handling).
  3. Add it to FN_SPECS in backend/config.ts (path, note, sample body) so it shows up in the Activity table and the Debug tab's picker.
  4. vt push.