Skip to content

Hosts and Routes

NEMAR answers on eight production hostnames, served by five deployments. This page is the map: which hostname is served by which deployment, which paths live on which host, and where a retired URL ends up.

If you are looking for what an endpoint does, the reference pages are Backend API, Data API and Zarr and edge access. This page answers the question before those: which host, and why that one.

nemar.org ──────────┐ public pages, the dataset browser
app.nemar.org ──────────┴─ Cloudflare Pages, from nemarOrg/website
api.nemar.org ──┐
data.nemar.org ──┤
zarr.nemar.org ──┼───────── ONE Cloudflare Worker, from nemarOrg/nemar-cli
mcp.nemar.org ──┘ (backend/, with D1, Vectorize, Workers AI, S3)
docs.nemar.org ─────────── this site, from nemarOrg/docs
dashboard.nemar.org ─────────── from nemarOrg/nemar-observability
HostnameServesDeployed fromAuthentication
nemar.orgHome, /discover, dataset landing pages, policiesnemarOrg/website, Cloudflare PagesAnonymous; responses are edge-cacheable
app.nemar.orgDashboard, upload, settings, admin, CLI authorizeThe same build and the same projectSession cookie, scoped to this host
api.nemar.orgThe backend APInemarOrg/nemar-cli backend/, Worker nemar-apiBearer API key, or the web session cookie
data.nemar.orgPublic dataset files, version manifests, archive zipsThe same Worker, data forkAnonymous
zarr.nemar.orgThe Zarr serving copies and their index documentsThe same Worker, zarr forkAnonymous
mcp.nemar.orgThe Model Context Protocol serverThe same Worker, mcp forkAnonymous
docs.nemar.orgThis sitenemarOrg/docs, Cloudflare Pages, built from main on mergeAnonymous, except /admin/* behind Cloudflare Access
dashboard.nemar.orgThe hub at /, the /observability health dashboard, and /citationsnemarOrg/nemar-observability for the first two; a separate legacy Pages project still serves /citationsAnonymous reads, plus one token-gated pipeline push. The public snapshot carries no private dataset ids

api, data, zarr and mcp are not four deployments. They are one Worker that forks on the hostname it was reached at, so a change to D1 access or to a service module lands on all four at once. Shared middleware is the exception, for the reason in the second bullet below.

HostnameForkWhat it dispatches to
api.nemar.orgapiThe full API app: the middleware stack, then the path mounts below
data.nemar.orgdataThe data sub-app at the root, so the public contract is data.nemar.org/<id>/<version>/... with no /data/ prefix. The bare root serves the catalog index
zarr.nemar.orgzarrA self-contained sub-app with its own tightly scoped CORS, Range pass-through and edge caching
mcp.nemar.orgmcpA self-contained sub-app: the origin gate and rate-limit bridge that the Streamable HTTP transport needs

Two properties of the fork are worth knowing, because both are deliberate:

  • The hostname is read from the request URL, never from the Host header. A forged Host: therefore cannot steer an api.nemar.org request into the data, zarr or mcp fork.
  • The zarr and mcp forks bypass the API middleware stack on purpose. The global CORS policy allows *.nemar.org broadly, which is right for the API and wrong for a host that browsers hit with credentials-free byte-range reads. Those two hosts declare their own. So the logger, security headers, CORS, rate limiter and maintenance-mode middleware run for api and data only: the data fork re-enters the API app to inherit them, while zarr and mcp are dispatched straight to their own sub-app.

Every fork is also reachable by path on the api host and on the workers.dev fallback, which is how a deploy is tested before a custom domain exists:

Path mountReaches
api.nemar.org/data/...The data fork’s handlers
<worker>.workers.dev/zarrproxy/<id>/zarr/<path>The zarr gateway
<worker>.workers.dev/mcpThe MCP transport endpoint, and only that endpoint

The production hostnames are built-in defaults in the fork table, each overridable by an environment variable (DATA_HOSTNAME, ZARR_HOSTNAME, MCP_HOSTNAME), which is what lets the staging Worker answer on the -test mirrors below without a code change.

nemar.org and app.nemar.org are one Astro build. The split is about the session cookie, not about the code: the cookie is issued with Domain=app.nemar.org so that it never attaches to byte-range fetches against data.nemar.org, to search requests against api.nemar.org, or to any future subdomain. That in turn keeps the public host anonymous, and therefore edge-cacheable.

Every path belongs to exactly one of the two hosts, and the middleware redirects it off the other: 301 for GET and HEAD, 307 for anything with a method and body to preserve.

These prefixes live on the app host:

PrefixWhy it is app-only
/login, /welcome, /onboardingSign-in and the account setup that follows it
/dashboard, /upload, /settingsRead and write the signed-in account through the host-scoped cookie
/adminAdmin and owner operations
/cliThe CLI device-authorization page the backend hands the CLI as its verification_uri
/authThe ORCID browser flow; the state, pending and session cookies are all host-scoped, and the OAuth redirect_uri host has to match
/api/auth, /api/admin, /api/v1Same-origin proxies for cookie-authenticated calls. Classified as marketing they would be redirected cross-origin and the cookie would not travel. /api/admin is reserved: the prefix is classified, but nothing is served under it yet
/dataset/<id>/collaboratorsPer-dataset access management

Two exceptions to the binary split, both of which exist because something was broken without them:

  • /api/notices is host-neutral. It feeds the site-wide notice banner, which renders on public and signed-in pages. Pinned to either host it would be redirected cross-origin from the other, where no CORS headers apply, and the banner would silently never appear.
  • An app-to-public redirect is suppressed when the request carries a session. Otherwise a signed-in user who clicked “Discover” was sent to the public host, where the cookie does not travel, and watched themselves get signed out for using the nav. The reverse is never suppressed: letting a cookie change what the public host serves would vary a shared cache entry per user.

Because the app host can serve a public route, the canonical URL is a property of the route rather than of the host that answered, so app.nemar.org/discover canonicalises to nemar.org/discover instead of competing with it.

RouteWhat it is
/Home
/discoverThe dataset browser: search, facet filters, sort
/dataset/<id>Dataset landing page, and the canonical DOI landing target. ?v=v1.0.0 selects a version
/dataset/<id>.mdThe same page as Markdown, for agents and for pasting into a prompt
/signupHow to create an account; links to /login on the app host
/about, /supportProject and contact pages
/privacy, /termsPolicies. The full set is under Policies
/api/noticesActive site-wide notices. Served on both hosts
/404Not found
RouteWhat it is
/llms.txtSite map written for language models
/robots.txt, /sitemap.xmlCrawler directives and the page index. Non-production hosts are noindex
/version.jsonThe deployed build version, matching the x-nemar-version response header
/og/dataset/<id>.png, /og/dataset/<id>.svgSocial card for a dataset

More agent-facing entry points, including the MCP server and the Zarr index, are collected in For agents and tools.

RouteWhat it is
/login, /login/verify, /login/pendingSign in with ORCID or an email code, and the states after it
/welcome, /onboardingFirst-run account setup: username, name, location
/dashboardYour datasets, requests and account state
/upload, /upload/successBrowser upload
/settingsProfile, email, ORCID link, API keys, upload access
/dataset/<id>/collaboratorsManage collaborators on a dataset you own
/cli/authorizeConfirm or deny a CLI device code
/auth/orcid/start, /auth/orcid/callback, /auth/orcid/completeThe ORCID browser flow
/admin, /admin/users, /admin/users/<username>, /admin/publication-requests, /admin/imports, /admin/noticesAdmin surface
/api/auth/..., /api/v1/<path>Same-origin proxies to the backend for the cookie session

On api.nemar.org, the API is mounted by prefix:

PrefixCoversReference
/authCLI sign-in, the device authorization grant, named API keys, web email codes, ORCIDBackend API
/usersYour own account, upload-access requestsBackend API
/datasetsThe dataset lifecycle: validate, upload, version, publish, searchBackend API
/sandboxThe sandbox training run required before a first uploadSandbox commands
/adminApprovals, DOIs, imports, sweeps, fleet governanceAdmin commands
/schemasPublished JSON Schemas, including the Zarr index schemaIndex contract
/openapi.jsonThe OpenAPI 3.1 document for this API, generated from the same schemas the server validates againstBackend API
/dataThe data plane, also served at the root of data.nemar.orgData API
/webhooksInternal callbacks from dataset CI. Not a public contract

Two more public endpoints sit directly on this host rather than under a prefix: GET /health for liveness, and GET /notices for the site-wide notices that the website’s /api/notices proxy reads.

Staging is a second Worker and a second Pages project, on the same zone. It is a real deploy, not a preview: it has its own database, its own dataset fixtures and its own DOI shoulder.

Staging hostnameProduction counterpart
test.nemar.orgnemar.org and app.nemar.org together, in single-host mode
api-test.nemar.orgapi.nemar.org
data-test.nemar.orgdata.nemar.org
zarr-test.nemar.orgzarr.nemar.org
mcp-test.nemar.orgmcp.nemar.org

The previous NEMAR site addressed datasets under /dataexplorer. Those URLs are permanently redirected, so a citation or a bookmark still resolves:

Old URLNowStatus
/dataexplorer/discover301
/dataexplorer/detail?dataset_id=<id>/dataset/<id>301
/docs, and six known /docs/<page> pathsThe matching page on docs.nemar.org. Anything else under /docs/ lands on the site root301
/citation-dashboarddashboard.nemar.org/citations/301
/resources, /tools, /members, /groups, /citationsThe legacy site at ww1.nemar.org302

The last row is 302 rather than 301 deliberately. Those sections have no counterpart on the current site, so they still serve from the legacy host, and that host will retire; a cached permanent redirect would outlive it with no way to reach the clients holding it.

The dataset id is passed through untranslated. /dataset/<id> resolves a legacy ds* accession to its NEMAR id with a real catalog lookup, and declines when no mirror exists rather than inventing an id.

One subtlety worth knowing if you are debugging a redirect: a request that arrives with a Referer on the legacy host is sent back there, on the reasoning that someone mid-session on the old site followed an absolute link and should not be ejected. A request with no Referer at all is treated as a citation and sent to the current site, because a missing Referer is the common case for a typed URL, a bookmark or a search result.

Everything above is derived from configuration and code, not from probing hosts. If you need to confirm or extend it, read these rather than sending requests:

FactSource
Which hostnames the Worker answers on, and the staging mirrorsnemar-cli backend/wrangler-sccn.toml, the routes blocks for the default and dev environments
The hostname forknemar-cli backend/src/services/host-routing.ts, plus the dispatch middleware in backend/src/index.ts
Backend path mountsThe api.route(...) calls in nemar-cli backend/src/index.ts
The app and public host split, redirects, canonical origins, legacy URLswebsite src/lib/host.ts, applied in src/middleware.ts
The website’s routeswebsite src/pages/
Website environment hostnameswebsite wrangler.toml, and .github/workflows/deploy-test.yml for staging
This site’s deployThe nemar-docs Cloudflare Pages project, git-connected to main. The wrangler.jsonc in the docs repo describes a planned Workers Static Assets deployment that is not live
The dashboard’s routesnemar-observability wrangler.toml