# Facet Facet hosts pages and small apps at https://.facet.page, private by default, shared with named people by email or @domain, or made public. This Facet runs pages only for now: deploying an app answers 403 apps_off. Pages work as usual. Every site can declare facts (one JSON-LD block with id="facts" on a page, or an app's /facts path) so an assistant can answer questions from the source instead of guessing. For a status, a number or a short answer about a site, call ask or facts first; fetch the HTML only when the declared facts do not cover the question. Without a key, any site URL's Link header points at its twin: /.well-known/facet/facts.json (and /.well-known/facet/facts.jsonld for a page), described by the schema at https://api.facet.page/ns/facts/1. ## How to connect - MCP: POST https://api.facet.page/mcp with `Authorization: Bearer facet_key_…` (Streamable HTTP, stateless). Without a token the answer is 401 with a WWW-Authenticate header pointing at https://api.facet.page/.well-known/oauth-protected-resource/mcp. - REST: https://api.facet.page/openapi.json. Same tools, same JSON. - API keys are created on the Facet machine with `npm run facet -- keys create `; the value is shown once. Older pk_local_… keys still work. - Sapphire connects by link and code: POST https://api.facet.page/oauth/device, the person types the code at https://id.facet.page/connect, then POST https://api.facet.page/oauth/token gives a key that can publish pages, change who can see them, make them public, and read and change the site data those pages keep, and nothing more. A connection approved before it could use site data must connect again for that; GET /v1/me says which connection a key is and whether it can (site_data); DELETE /v1/connection revokes it. ## The flow 1. describe_deploy: the descriptor schema, runtimes (Node 24, Python 3.13), sizes, limits, reserved names, rate card. 2. A page: deploy {kind: "doc", name, files: [{path: "index.html", content_base64}]}. Live when the call returns. 3. An app: create_upload → PUT the .tar.gz to put_url → deploy {upload_id, descriptor} → get_deploy until live or failed. Apps need a saved card first (billing_link {kind: "setup"}). 4. share {site, add: ["alice@example.com"], notify: true}; or add_token {label} for a program; set_visibility public needs a saved card. 5. facts {site} and ask {site, question} read the declared facts. ask returns answer: null with candidates and a hint when nothing matches; ask again by a candidate id. 6. usage, set_spend_limit, billing_link, logs, rollback, set_secrets, delete_app (confirm: true; an export link is returned). ## App navigation In an app header, use ‹ Facet in the same tab to open Facet's signed-in app home. Facet chooses the configured sign-in host and port. Keep the app list on Facet home and preserve the app's access rules. ## Users - list_users (GET /v1/users) lists known people, their current app access and recorded sign-ins and app visits; it never reads app records, photo files, secrets, billing or keys. Domain grants are listed separately because a grant cannot tell us every person at a domain. Anonymous public visits are counted separately and cannot identify a person. - A full key may use scope "account" (the default) for its own apps. Publish keys, including Sapphire connections, cannot read the directory. Reading scope "platform" needs a separate operator-created key with scope "admin" and a current administrator grant; an existing full key never gains this access. Admin keys may only read the directory and GET /v1/me. They cannot build, publish, share, export, read app data or change any setting. - Filter by site or q (up to 200 characters); limit is 1–100 (default 100), offset starts at 0, and next_offset gives the next page when one exists. Only known people are listed: anonymous visitors and people at a granted domain who have never signed in are not identified. Visits begin when this feature is installed; there is no earlier visit history. ## Site data A page can keep small JSON records. It declares its collections in one