hyphertext/docs

Runtime Endpoints

Everything above is for you, the builder, acting through your own MCP session. These two endpoints are different: they're called by a published page's own client-side JS, in a visitor's browser, at runtime — long after your MCP session has ended. If you write a page that needs to save data or call a third-party API, this is what its own JS should call.

Why a separate surface

A page visitor isn't a Hyphertext account and has no MCP session or auth token to send. So instead of Supabase auth, both endpoints are scoped by public_app_key — a per-project identifier different from the page's own id, returned by get_page. Write it into the page's own JS (or a project file) so the live page can use it. The key only ever authorizes that one project's own data — never anything else.

Database — /api/db/{public_app_key}/{collection}

Plain REST CRUD over the same project_db_records store you seed at build time with db_insert/db_query. A visitor loading the page reads and writes through this endpoint, so data persists across visits and across devices — not just in the browser's own storage. Capped at 5,000 rows per (page_id, collection).

fetch(`/api/db/${APP_KEY}/todos`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text: "buy milk" }),
});

Secret-backed proxy — /api/proxy/{public_app_key}

Lets the page's own JS call a third-party API using a project secret without the value ever reaching the browser. Send the request you want proxied, with the literal placeholder "{{SECRET}}" wherever the real value belongs (typically in an Authorization header) — the server substitutes it in before forwarding, server-side.

fetch(`/api/proxy/${APP_KEY}`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    key: "OPENAI_API_KEY",
    target_url: "https://api.openai.com/v1/chat/completions",
    method: "POST",
    headers: { "Authorization": "Bearer {{SECRET}}" },
    body: { model: "gpt-4o-mini", messages: [...] },
  }),
});

The proxy carries SSRF guardrails since target_url is visitor-suppliable: https-only, blocks localhost/private-IP/cloud-metadata hosts, a request timeout, and a response size cap.

Public-exposure secrets

A secret declared with exposure: "public" (e.g. a Stripe publishable key, a domain-restricted Maps key) skips the proxy entirely — it's inlined directly into the served page as window.HYPHERTEXT.env, since it's designed to be client-visible. Never use public exposure for a real secret credential.