Ga naar inhoud

The netnl service: endpoint, terms, limits, retention

What a tenant of the hosted netnl facade can count on. These are the published values behind the deployment Mark runs; anyone self-hosting the facade sets their own (every value below is an environment variable — see deploy-facade.md).

Ratified 2026-09-05: the published policy is exactly what the code defaults to, so documentation and behaviour cannot drift. The private beta exists to test whether these numbers are right, and any of them can be changed with an environment variable and a pod restart — no release needed.

Endpoint

https://api.westerweel.work — the primary public hostname, fronted by a Cloudflare Tunnel. A Tailscale Funnel *.ts.net hostname is kept up in parallel as a fallback; the beta runbook hands out the primary.

The facade serves the Batch API v2 routes at the root, so this bare base URL is what goes in INTERNETNL_ENDPOINT — the CLI appends /requests, /requests/{id}, /requests/{id}/results and /metadata/report itself. Do not append /api/batch/v2: that prefix belongs to a bare upstream instance, not to this facade.

Terms

Measure your own hosts freely. Measure someone else's only where the measurement is one their operator already invites — and then only once.

A measurement makes the upstream instance connect to the target from the outside, from this operator's address. That is why the second half is narrower here than the tool's own rule: what a tenant does lands on infrastructure someone else pays for and answers for.

"Already invites" means the domain sits in a category that is publicly and routinely measured this way — Dutch government domains, for instance, which the government measures twice a year and publishes per domain. It does not mean "is reachable over the internet".

Repeated or scheduled measurement of a domain you do not operate is out of bounds regardless, as is presenting a batch verdict about someone else's domain as an audit finding. These are the conditions every credential is issued under, and the ground for revoking one.

Credentials are per tenant, issued by hand (netnl-admin user add) or automatically on a qualifying donation (see supporter-key.md). They are not transferable.

Getting a credential

Two routes, both ending in the same kind of credential:

  1. Ask. Mail the operator with the domains you intend to measure and who you are. Beta credentials are issued by hand, to people whose ownership of those domains is plausible — the terms above are the whole admission test. The operator's side of this is beta.md.
  2. Donate. A qualifying donation mints a lifetime credential automatically and mails it to the donor; see supporter-key.md. This is self-service and needs no correspondence.

Either way you receive one INTERNETNL_CREDENTIAL string (username:password) plus this endpoint, and the credential mail contains nothing else. A lost credential is reissued, not recovered: the operator runs netnl-admin user reissue <name> and the old password stops working.

Credentials are revoked when the terms are broken, on request, or when a tenant's use is clearly outside what one homelab instance can carry.

Limits

All tenant limits below are per credential, not shared across the facade: your neighbour's busy hour does not spend your budget.

What Value Variable
Submissions per hour, per tenant 10 NETNL_RATE_LIMIT
Domains per request 500 NETNL_MAX_DOMAINS
Concurrent runs, per tenant 2 NETNL_MAX_CONCURRENT
Anonymous page: requests per hour 6 NETNL_DEMO_MAX_PER_HOUR
Anonymous page: concurrent runs 2 NETNL_DEMO_MAX_CONCURRENT
Anonymous page: requests per IP per hour 2 NETNL_DEMO_PER_IP_PER_HOUR
Anonymous page: polls per IP per hour 120 NETNL_DEMO_POLLS_PER_IP_PER_HOUR

The browser page at https://mwest2020.github.io/internetnl-cli-demo/ is not a demonstration version: it is this service, at v1.0.0, measuring for real against the same upstream instance, with an anonymous credential and tighter bounds. Nothing a visitor receives calls it a demo. The NETNL_DEMO_* variable names and the /demo/* routes are the historical spelling and still on the wire — renaming them means changing the browser page in the same release. | Supporter keys minted per hour | 20 | NETNL_SUPPORTER_MAX_PER_HOUR | | Delivery attempts per supporter key | 3 | NETNL_SUPPORTER_MAX_ATTEMPTS |

How much runs at once, in total

There is no facade-wide ceiling: the limits above are per credential, so the total work in flight is 2 × active tenants, plus at most 2 for the anonymous browser page. One facade process (a single replica over one SQLite file) serialises the bookkeeping, but not the measurements themselves.

Behind it all sits one upstream batch instance, and its own capacity has never been measured under load — that measurement is the point of the private beta (add-measurement-api, task 4.3). Until it exists, the honest statement is: a handful of tenants is fine, and nobody knows where the knee is. If the beta shows the instance saturating before the per-tenant caps bite, the fix is a facade-wide ceiling on top of the per-tenant ones, not lower per-tenant numbers.

The concurrency ceiling is the one that matters: there is a single upstream batch instance behind this facade, and it is the scarce resource. Everything else is a fair-use bound so one tenant — or the anonymous page — cannot take it all.

What happens when you hit one

Nothing queues. A submission over any of these bounds is refused immediately with 429 and a machine-readable rate-limited code; the body says which bound and what the limit is ("2 runs already in progress; the limit is 2"). There is no waiting room, no retry-after scheduling on our side, and no partial acceptance — the request simply did not happen, and retrying is the caller's decision.

That is deliberate. A queue on a facade in front of a single batch instance would hide the scarcity rather than communicate it: submissions would sit in a buffer the caller cannot see, time out somewhere in the middle, and turn a crisp "not now" into an unbounded wait. Refusing fast keeps the caller in control of their own retry policy, and keeps the facade stateless about work it has not accepted.

Before refusing on concurrency, the facade first refreshes your non-terminal runs against upstream — so a slot freed by a run that finished a minute ago is noticed, and you are not refused on stale bookkeeping.

Retention

What Window Variable
Tenant results 7 days NETNL_RESULT_RETENTION_DAYS
Anonymous-page results 24 hours NETNL_DEMO_RETENTION_HOURS
Audit records 90 days NETNL_AUDIT_RETENTION_DAYS

Windows are applied by netnl-admin prune, which runs on a cron; the cron cadence is what actually bounds these in practice (see deploy-facade.md). The audit trail is append-only — a database trigger refuses UPDATE and DELETE, so only the prune pass's own retention window removes rows.

Fetch your results before the window closes. The facade stores what the upstream instance returned; it is not an archive.

Batch results are not website results

A score from this API will not always match what internet.nl's website shows for the same domain: the batch API skips the connection test, does DNSSEC without the registrar lookup, and does no A/AAAA prechecks. The full list of differences is in self-hosted.md.

Not an SLA

Homelab-grade, best-effort, no managed-service guarantee: one upstream instance, one facade, one operator, no on-call. It can be down while Mark is asleep. Free tenants and supporter keys get the same treatment — a donation buys a lifetime credential, not a support contract.