openvibers/openvibe.domains README.md
README.md
OpenVibe.Domains
Name it, price it, run it.
Live site: https://openvibe.domains · API docs: https://openvibe.services · For AI: https://openvibe.domains/llms.txt
Run it: npm ci && npm run dev (Node 22 or newer) · Tests: npm test
Status: v1 is the service skeleton (see [STATUS.json](STATUS.json)); the pages are plain placeholders.
Domain: openvibe.domains · Port: 5070 · Service id: domains · Env prefix: DOMAINS
License: AGPL-3.0-only (same as every OpenVibe service).
OpenVibe.Domains is a stateless read-only front over two OpenVibe services. For a name it shows the registry's
availability and every registrar's prices for that ending, with one-, three- and five-year totals; every ending has
its own page; every domain has a page with its public registration history.
There is no database, no sign-in, no session and no event stream. Nothing is stored and nothing is written.
Pages (v1)
| Route | What it shows |
|---|---|
| / | the first page of OpenVibe.Host's price catalog (popular endings), plus OpenVibe.Tools' public "most looked up" and "expiring soon" lists when it answers |
| /search?q= | registry status for the name, and every registrar's first-year, three-year and five-year totals, sorted by the three-year total; noindex because every query is a page |
| /tld/:tld | one ending (.dev, co.uk) with every registrar's prices, the five-year total and the renewal-trap mark |
| /.<ending> | 301 to /tld/<ending> (for example /.dev → /tld/dev) |
| /d/:domain | a full domain: its public history from OpenVibe.Tools and its ending's prices from OpenVibe.Host |
| anything else | the 404 page |
All of it is server-rendered and readable without JavaScript. The shell is the OpenVibe Frame (openvibe-shared/shell):
theme loader, navbar mounted into #navbar-mount, the shared server-rendered footer, service id domains.
The control panel (v2)
/panel is a signed-in person's own (sign-in with OpenVibe.Network, client domains).
- Connect: paste a Cloudflare API token (Edit zone DNS + Zone Read) or a Porkbun API key pair (create keys at
porkbun.com/account/api, then switch on API access for each domain). Domains verifies it, then stores it as a
secret in your own Network project (OpenVibe.Domains, ADR-056), granted todomainsfor calls to that provider
only (https://api.cloudflare.comorhttps://api.porkbun.com). It is revealed per call (network.secret.use)
and never kept here. Porkbun's pair is one JSON secret, parsed only inside its client. - Zones and records: zones are synced into
domains_zones(Porkbun's domains are its zones); records are read
live. - Proposals: every change is a
domains.proposal@1: create, update or delete records, why, what could break, and
7 days to decide. A registrar change is a nameserver list update (NS) or a DNSSEC DS create/delete (DS), applied
through the registrar API. Approve on/panel/p/:idor in the Action Center. Changes apply in order; a record or
nameserver list that moved since the proposal stops the run (stale). An applied (or part-applied) proposal rolls
back in one step. - API:
GET /api/v1/panel/connections,/api/v1/panel/zones,/api/v1/panel/zones/:conn/:zone/records,
GET|POST /api/v1/proposals,POST /api/v1/proposals/:id/decide|rollbackwith your Network token; Network's Action
Center readsGET /api/v1/actions?subject=(domains.actions.read) and decides (domains.proposal.decide).
API
Everything under /api/v1, read-only, CORS-open (Access-Control-Allow-Origin: *), cached publicly for five
minutes. A bad name is RFC 9457 application/problem+json; an ending the catalog does not know is 404.
| Route | Answer |
|---|---|
| GET /api/v1/search?q= | { name, status, rows } — name is { label, tld, ascii, display }, status is the registry's answer, rows are the priced registrar rows |
| GET /api/v1/tlds?q=&kind=&sort=&page= | OpenVibe.Host's list as it answers it: { tlds, next_cursor }. Sorts: popular, tld, register, renew, five_year |
| GET /api/v1/tlds/:tld | { tld } with its prices priced like the page |
| GET /api/v1/domains/:domain | { name, history, prices } — history from OpenVibe.Tools, prices for that domain's ending only |
/api/v1 and /search share a per-IP limit of 120 requests a minute (express-rate-limit).
What "priced" means
OpenVibe.Host's check rows carry register, renew, five_year, registrars, currency, checked_at, tld,
display and kind. Each row gains:
y1— first year:register;y3— three years:register + 2 × renew;y5— five years: the catalog'sfive_yearwhen it has one, elseregister + 4 × renew;trap—renew ≥ 2 × registerandrenew − register ≥ 5: the cheap-first-year renewal trap.
A total that needs a missing price is null, never NaN. Money is rounded to cents. Only USD rows are compared and
sorted (by y3, rows without a y3 last); other currencies are kept after them and flagged currency_other: true.
The data it reads
| Source | Endpoint | Used for | Cache |
|---|---|---|---|
| OpenVibe.Host | GET /api/v1/catalog/domains?sort=…&cursor=… | the catalog's ending list and the sitemap | 10 minutes |
| OpenVibe.Host | GET /api/v1/catalog/domains/:tld | one ending's registrar prices and history | 10 minutes |
| OpenVibe.Host | GET /api/v1/catalog/domains/check?name= | a name's registry status and priced endings | 2 minutes |
| OpenVibe.Tools | GET /api/v1/domains/ | the public most-looked-up and expiring-soon lists | 10 minutes |
| OpenVibe.Tools | GET /api/v1/domains/:domain | a domain's public snapshots and changes | 10 minutes |
Both clients take an injectable fetchImpl, time out after 8 seconds, and keep a small in-process Map cache with a
2,000-entry cap (oldest insertion out). A failure is null and one short log line — never an exception in a page.
The upstream URLs come from OV_HOST_INTERNAL_URL / OV_TOOLS_INTERNAL_URL, or the internal origins in the pinned
OpenVibe.Contracts manifests, with https://openvibe.tools as the Tools fallback.
OpenVibe.Tools keeps RDAP's public registration facts only (registrar, dates, status codes, name servers, DNSSEC) and
publishes a domain on its lists only after at least five lookups. This service adds no data of its own.
Configuration
Copy [.env.example](.env.example) to the environment file (/etc/openvibe/domains.env in production):
| Variable | Default | What |
|---|---|---|
| NODE_ENV | development | production selects the production base URL |
| PORT | 5070 | listen port |
| HOST | 127.0.0.1 | listen address |
| BASE_URL | https://openvibe.domains in production | public origin used for canonical URLs, the sitemap and llms.txt |
| TRUST_PROXY | 2 | how many proxies to trust (nginx and the CDN) |
| OV_HOST_INTERNAL_URL | the Host manifest's internalOrigin | OpenVibe.Host's internal API |
| OV_TOOLS_INTERNAL_URL | the Tools manifest's internalOrigin | OpenVibe.Tools' internal API |
| DOMAINS_RATE_LIMIT_PER_MIN | 120 | the shared /api/v1 and /search per-IP budget |
| DOMAINS_UPSTREAM_TIMEOUT_MS | 8000 | the two data clients' timeout |
Run it
npm install
npm test # every test/*.test.js, each in its own process, with stub upstreams
npm run dev # http://localhost:5070
npm start # production
The tests need no database, no network and no running OpenVibe site. node --check every .js file is the other
gate.
Machine endpoints
| Route | What |
|---|---|
| /api/health | { status: "ok", service: "openvibe-domains", version } |
| /api/ready | truthful readiness (openvibe-shared/ready): ready while the process serves; a Host or Tools failure makes status: "degraded" and names it, never 503 |
| /release.json, /release-metrics | the release manifest and the tab collector (openvibe-shared/release) |
| /metrics | Prometheus text format, loopback only (openvibe-shared/metrics) |
| /shared/* | this service's own pinned OpenVibe Frame files |
| /robots.txt | allow everything, name the sitemap |
| /sitemap.xml | / and one /tld/<ending> per ending in every page of the Host catalog; lastmod from the release |
| /llms.txt | what the site is, its pages and its API |
Deploy
deploy/systemd/openvibe-domains.service— userubuntu,WorkingDirectory=/opt/openvibe.domains,
EnvironmentFile=/etc/openvibe/domains.env, port 5070.deploy/nginx/openvibe.domains.conf— TLS,www→ apex 301, proxy to127.0.0.1:5070, security headers, GET-only
/api/v1, loopback-only metrics.- CI:
.github/workflows/ci.ymlruns the shared syntax/contracts/security jobs; there is no database service.
License
AGPL-3.0-only. See [LICENSE](LICENSE).
<!-- versions:start -->
- openvibe-contracts: v0.419.0
- openvibe-sdk: v0.161.0
- openvibe-shared: v4.238.0
<!-- versions:end -->