# Netzwerkzeug API

Canonical v1 base: `/api/v1/`

## Envelope

Every endpoint returns:

```json
{"ok":true,"data":{},"error":null}
```

Errors return `ok: false`, `data: null`, and an error object with
`code` and `message`.

## v1 contract

All v1 endpoints use `POST` with exactly
`Content-Type: application/json`. Requests are JSON objects, limited to 64 KiB;
fields reject control characters and have endpoint-specific limits. Responses
are `application/json`, no-store, same-origin only, with no CORS headers.

Network-backed endpoints use the established validated network handlers, so DNS-worker isolation, public-target validation, SSRF blocking,
DNS-rebinding protection, redirect revalidation, timeouts, response caps,
rate limits, and concurrency limits are shared rather than duplicated.

API-backed UI forms transmit their inputs to this service, including the
encode/decode form at /encode/. Browser-only tools are explicitly labelled;
the legacy /decode.html page performs its transformations in the browser.
Payloads are processed transiently and are not intentionally persisted.

## UI/API parity

| UI route | API route | Method | Request |
|---|---|---|---|
| `/ip/` | `/api/v1/ip/` | POST | `{"cidr":"192.168.42.17/27"}` |
| `/cidr/` | `/api/v1/cidr/` | POST | `{"operation":"network\|split","cidr":"...","target":"20"}` |
| `/dns/` | `/api/v1/dns/` | POST | `{"domain":"example.com"}` |
| `/dns-delegation/` | `/api/v1/dns-delegation/` | POST | `{"domain":"example.com"}` |
| `/dns-propagation/` | `/api/v1/dns-propagation/` | POST | `{"domain":"example.com","record_type":"A"}` |
| `/domain-inspector/` | `/api/v1/domain-inspector/` | POST | `{"domain":"example.com"}` |
| `/diagnostics/` | `/api/v1/diagnostic-intelligence/` | POST | `{"domain":"example.com"}` |
| `/dns-consistency/` | `/api/v1/dns-consistency/` | POST | `{"domain":"example.com","record_type":"A"}` |
| `/certificate-transparency/` | `/api/v1/certificate-transparency/` | POST | `{"domain":"example.com","scope":"domain_and_subdomains","limit":50}` |
| `/dnssec-chain/` | `/api/v1/dnssec-chain/` | POST | `{"domain":"example.com"}` |
| `/internet-intelligence/` | `/api/v1/internet-intelligence/` | POST | `{"domain":"example.com"}` |
| `/packetgauge-lookup/` | `/api/v1/packetgauge-lookup/` | POST | `{"input":"example.com"}` |
| `/reverse-dns/` | `/api/v1/reverse-dns/` | POST | `{"ip":"8.8.8.8"}` |
| `/http-headers/` | `/api/v1/http-headers/` | POST | `{"url":"https://example.com/"}` |
| `/redirects/` | `/api/v1/redirects/` | POST | `{"url":"https://example.com/"}` |
| `/tls/` | `/api/v1/tls/` | POST | `{"host":"example.com","port":"443"}` |
| `/port/` | `/api/v1/port/` | POST | `{"query":"https"}` |
| `/mac/` | `/api/v1/mac/` | POST | `{"mac":"00:1B:63:00:00:00"}` |
| `/mail-dns/` | `/api/v1/mail-dns/` | POST | `{"domain":"example.com","selector":"default"}` |
| `/dnssec/` | `/api/v1/dnssec/` | POST | `{"domain":"example.com"}` |
| `/rdap/` | `/api/v1/rdap/` | POST | `{"resource":"example.com"}` |
| `/bgp/` | `/api/v1/bgp/` | POST | `{"resource":"AS15169"}` |
| `/smtp-test/` | `/api/v1/smtp-test/` | POST | `{"domain":"example.com"}` |
| `/http-security/` | `/api/v1/http-security/` | POST | `{"url":"https://example.com/"}` |
| `/domain-health/` | `/api/v1/domain-health/` | POST | `{"domain":"example.com"}` |
| `/internet-identity/` | `/api/v1/internet-identity/` | POST | `{"ip":"8.8.8.8"}` |
| `/ping/` | `/api/v1/ping/` | POST | `{"host":"example.com"}` |
| `/traceroute/` | `/api/v1/traceroute/` | POST | `{"host":"example.com"}` |
| `/network-path/` | `/api/v1/network-path/` | POST | `{"url":"https://example.com/"}` |
| `/port-connectivity/` | `/api/v1/port-connectivity/` | POST | `{"host":"example.com","port":"443"}` |
| `/http-protocol/` | `/api/v1/http-protocol/` | POST | `{"url":"https://example.com/"}` |
| `/mail-health/` | `/api/v1/mail-health/` | POST | `{"domain":"example.com"}` |
| `/ipv6/` | `/api/v1/ipv6/` | POST | `{"operation":"reverse","input":"2001:db8::1"}` |
| `/cidr-overlap/` | `/api/v1/cidr-overlap/` | POST | `{"input":"192.0.2.0/24,192.0.2.0/25"}` |
| `/firewall-rule/` | `/api/v1/firewall-rule/` | POST | `{"input":"{...}"}` |
| `/encode/` | `/api/v1/encode/` | POST | `{"operation":"base64-encode","input":"hello"}` |
| `/eicar-mail/` | `/api/v1/eicar-mail/` | POST | `{"domain":"example.org","variant_id":"eicar-txt"}` |

## Diagnostic Intelligence graph

The Diagnostic Intelligence response may include the optional additive
`diagnosis_graph` field. It uses schema `packetgauge.diagnosis-graph.v1` and
contains stable node/edge references, root finding references, blocked-check
references, and bounded validation metadata. The existing `findings`,
`measurement_limitations`, status, completeness, evidence, and technical
details remain authoritative and unchanged. A graph validation failure is
reported as `graph_status: "UNAVAILABLE"` with no partial nodes or edges; it
does not fail the diagnostic response.

The graph is reference-based and deterministic. `DEPENDS_ON` and `BLOCKS` are
semantic prerequisite relationships, not causal claims. Stage B emits no
`CAUSES` edges.

Diagnostic Intelligence also returns additive `checks` and
`check_completeness` fields. `checks` contains one normalized object for every
applicable check, including PASS, FAIL, BLOCKED, UNAVAILABLE, INDETERMINATE,
and NOT_TESTED results. `check_completeness` contains execution counts only;
these counts are orchestration metadata and never diagnostic evidence.
The Diagnostic Intelligence request-wide orchestration deadline is 15 seconds;
this is a maximum, not a target runtime. Individual collector budgets and the
two-worker concurrency bound remain unchanged.

## BGP / ASN / RPKI

`POST /api/v1/bgp/` accepts a public IPv4 or IPv6 address, normalized prefix, or ASN in `AS15169` or `15169` form. The bounded response reports the normalized query, observed prefix, origin ASN, organization, RIPE NCC registry context, up to ten related prefixes, and an RPKI state of `VALID`, `INVALID`, or `UNKNOWN`. RPKI is never inferred: unavailable validator data is reported as `UNKNOWN` with evidence.

The worker uses fixed RIPE NCC RIPEstat endpoints over HTTPS, validates the fixed upstream hostname and all resolved addresses once per request, rejects redirects, caps each response at 64 KiB, allows at most three upstream calls, and enforces the existing 12-second aggregate request deadline with 2-second connect/4-second per-call limits.

## Local operations

### IP

`POST /api/v1/ip/`

Request: `{"cidr":"192.168.42.17/27"}`

Returns family, normalized network prefix, broadcast for IPv4, address count,
usable host count, and classification. IPv6 arithmetic returns the total as a
bounded exponent string.

### CIDR

`POST /api/v1/cidr/`

Use `operation: "network"` for one network or `operation: "split"` with a
numeric `target` prefix (a number or a numeric string). Splits are capped at 4096 IPv4 networks; IPv6 split
API support is rejected explicitly until its bounded representation is added.

### Encoding

`POST /api/v1/encode/`

Supported operations: `url-encode`, `url-decode`, `base64-encode`,
`base64-decode`, `hex-encode`, `hex-decode`, `sha256`, and `sha512`.
Input is capped at 64 KiB of UTF-8 bytes. Leading/trailing whitespace and line
breaks are preserved exactly; empty text is allowed. Hashes use the input's
UTF-8 bytes. Decoding returns UTF-8 text; valid Base64/hex/URL-encoded bytes
that are not UTF-8 receive `400 INVALID_INPUT`, never a server error or silently
replaced bytes.

## Network endpoint behavior

The v1 DNS, reverse DNS, HTTP, redirect, TLS, port, MAC, mail DNS, DNSSEC,
and RDAP routes use the same validators and response structures as the


## SMTP transport analyzer

`POST /api/v1/smtp-test/` accepts a public mail domain in the `domain` field. If the value has public MX records, the analyzer tests the bounded MX transport; if it is a public mail hostname without MX records, that hostname is tested directly on TCP/25. No DATA, AUTH, VRFY, EXPN, relay test, or recipient enumeration is performed.

## EICAR mail scanner test

`POST /api/v1/eicar-mail/` accepts only a public `domain` and exactly one selected `variant_id`: `eicar-txt`, `eicar-zip`, `eicar-tar`, `eicar-tar-gz`, or `eicar-bzip2`. The server constructs the recipient as `postmaster@` plus the validated domain, reads only the five fixed files under `/files`, uses a fixed sender and subject, resolves public MX targets, and connects directly to public TCP/25. Arbitrary recipient fields, SMTP hosts, ports, paths, uploads, and multiple attachments are rejected.

## EICAR delivery limitations

Direct-to-MX delivery can be rejected by SPF, DKIM, DMARC, reverse-DNS policy, reputation, anti-spam policy, or outbound TCP/25 filtering. Such rejection is reported as a controlled upstream result; the service does not weaken recipient security or automatically change SPF, DKIM, or DMARC.

## Limits and errors

- JSON body: 64 KiB; response: 1 MiB.
- Network query fields: 2048 characters; RDAP resource: 253 characters.
- Network operations retain the existing per-client expensive-operation rate and
  concurrency limits.
- Local APIs reject unsupported methods, content types, fields, and malformed
  schemas.
- Errors include `INVALID_INPUT`, `METHOD_NOT_ALLOWED`,
  `UNSUPPORTED_MEDIA_TYPE`, `REQUEST_TOO_LARGE`, `RESPONSE_TOO_LARGE`,
  `RATE_LIMITED`, `TIMEOUT`, `SERVICE_UNAVAILABLE`, and
  `INTERNAL_ERROR`. A `SERVICE_UNAVAILABLE` answer that carries a `Retry-After` header means the remote service (for
  example an RDAP registry) is rate limiting this host; wait that many seconds. A failed outbound connection adds `reason` (for example `CONNECTION_TIMEOUT`,
  `TLS_HANDSHAKE_FAILED`) and `addresses_tried` to the error object.
- No CORS, no authentication, no arbitrary target override, no shell execution,
  and no sensitive-payload logging by the API.

## RDAP and WHOIS owner facts

`POST /api/v1/rdap/` returns `summary` and `raw`. Besides the registry fields, the
summary carries the owner facts the registry publishes, all additive and all
sanitised (control characters removed, lengths capped):

- `registrant_state`: `published`, `redacted` (the registry withholds it), or
  `absent` (an RDAP record without a registrant, as thin registries return), and
  `registrant` (`name`, `organization`, `country`) only when published.
- `registrar`, `registrar_iana_id`, `abuse` (`email`, `phone`).
- `contacts`: up to 12 entities with `roles`, `name`, `organization`, `country`,
  `email`, `phone`; withheld values are flagged with `redacted`, never returned.
- `secure_dns` (delegation signed), `nameserver_details` (name with IPv4/IPv6
  addresses, only valid addresses), `notices` (registry notices), and for IP and
  AS lookups `cidrs`, `parent_handle`, `ip_version`.

Domains whose registry has no RDAP service use a WHOIS fallback that returns the
same `summary` shape for the conventional fields (registrar, registrant when
present, dates, status, name servers); fields a response does not carry are
absent and no claim is made about them.

### Diagnostic findings and observations

Diagnostic Intelligence keeps its existing response shape and rule IDs. INFORMATION records are contextual observations, not confirmed target problems: differing usable DNS answers, parent/recursive child NS differences, empty or null MX, absent mail policies, short certificate lifetime and missing optional HTTP policies. Clients must inspect finding status rather than treating every rule ID as an error. PASS on a check means a completed measurement, not a universal security certification. Explicitly invalid policies or certificates, direct authority failures and observed transport failures retain their warning/failure classification. Incomplete measurements remain separate and can make a PASS diagnosis PARTIAL.

Empty MX does not by itself prove failed mail delivery; SMTP has an implicit-MX fallback ([RFC 5321 §5.1](https://www.rfc-editor.org/rfc/rfc5321.html#section-5.1)). A DNS reply remaining truncated after the bounded TCP fallback is not used as a complete delegation RRset ([RFC 2181 §9](https://www.rfc-editor.org/rfc/rfc2181.html#section-9)).
