# how.is — API reference

Base URL: `https://how.is` · OpenAPI: `https://how.is/openapi.json` · MCP: `https://how.is/mcp` · Short guide: `https://how.is/llms.txt`

Domain and IP intelligence priced per call. Data comes from the live
[dns.pizza](https://dns.pizza) engine; site status and page watch are fetched live by how.is.

## How a call works

1. Call any endpoint — `POST` with a JSON body, or `GET` with the same field as a query parameter.
2. Without payment you get **HTTP 402**. The body lists every payment rail that works right now,
   and the `PAYMENT-REQUIRED` header carries x402 v2 terms.
3. Pay, repeat the same request, and get `{"result", "data", "receipt"}`.

The **free sample** for each check (listed below) returns real data with no payment, limited to
20 per hour per caller. Use it to learn the response shape.

## Paying

- **x402, per call, no account** — network `eip155:84532` (Base Sepolia (testnet — test USDC, no real value)). Call any endpoint; the 402 carries a `PAYMENT-REQUIRED` header (base64 JSON, x402 v2, also copied into the body as `x402`). Sign it with any x402 client and retry with `PAYMENT-SIGNATURE: <base64 payload>`.
- **Prepaid key** (Stripe test mode — test cards only) — a person buys credit by card: `POST https://how.is/topup/checkout {"amount": 10}` → `checkout_url`; after payment the key is shown on how.is. Send it as `Authorization: Bearer <key>`. Each key has a daily spend cap (default $0.10); past it you get a 402 until 00:00 UTC.

## Status codes

| code | meaning | charged? |
|---|---|---|
| 200 | result delivered | yes (free for samples) |
| 400 | bad input; body has `schema` and `example_body` | no |
| 402 | payment required, or daily cap reached | no |
| 404 | unknown path; body lists every endpoint | no |
| 429 | rate limited; see `retry_after_s` | no |
| 502 | upstream failed; `refunded: true` | refunded |
| 503 | upstream warming a cold cache; retry after `Retry-After` seconds | refunded |

## Checks

### Domain profile (bundle) <a id="domain_profile"></a>

`POST /v1/domain/profile` · **$0.025** · MCP tool `domain_profile`

Everything about a domain in one call: DNS, WHOIS, TLS, DNSSEC, email auth, security score, blacklist status, plus IP intelligence for its A record.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.sections.{dns,whois,tls,dnssec,email-auth,security-score,blacklist,ip}; data.errors names any section that failed; data.billing says what was delivered.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/profile?domain=example.com`

```
curl -X POST https://how.is/v1/domain/profile -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### DNS records <a id="domain_dns"></a>

`POST /v1/domain/dns` · **$0.005** · MCP tool `domain_dns`

A, AAAA, MX, NS, TXT and other records for a domain, with resolve timing.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data.records keyed by record type (A, AAAA, MX, NS, TXT, ...); data.data.responseTime.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/dns?domain=example.com`

```
curl -X POST https://how.is/v1/domain/dns -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### WHOIS / RDAP <a id="domain_whois"></a>

`POST /v1/domain/whois` · **$0.005** · MCP tool `domain_whois`

Registrar, creation and expiry dates, nameservers and status codes for a domain.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data.data: the parsed WHOIS/RDAP record (registrar, dates, nameservers, status).
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/whois?domain=example.com`

```
curl -X POST https://how.is/v1/domain/whois -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### TLS certificate <a id="domain_tls"></a>

`POST /v1/domain/tls` · **$0.005** · MCP tool `domain_tls`

Certificate validity, issuer, protocol and days until expiry for a domain.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data: valid, issuer, subject, subjectAltNames, validFrom, validTo, daysUntilExpiry, protocol, cipher, selfSigned.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/tls?domain=example.com`

```
curl -X POST https://how.is/v1/domain/tls -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### DNSSEC <a id="domain_dnssec"></a>

`POST /v1/domain/dnssec` · **$0.005** · MCP tool `domain_dnssec`

Whether DNSSEC is signed and validates for a domain.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data: isSigned, isValid, validationStatus, chainOfTrust, dsRecords, dnskeyRecords, errors, warnings, recommendations.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/dnssec?domain=example.com`

```
curl -X POST https://how.is/v1/domain/dnssec -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### Email authentication <a id="domain_email_auth"></a>

`POST /v1/domain/email-auth` · **$0.005** · MCP tool `domain_email_auth`

SPF, DKIM and DMARC records and posture for a domain.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data: spf, dkim, dmarc, mx, overallScore, overallStatus, recommendations.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/email-auth?domain=example.com`

```
curl -X POST https://how.is/v1/domain/email-auth -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### Security score <a id="domain_security_score"></a>

`POST /v1/domain/security-score` · **$0.005** · MCP tool `domain_security_score`

A letter grade and point score for a domain security posture, with the scored items behind it.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data: grade, overallScore, maxPossibleScore, categories[] of scored items.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/security-score?domain=example.com`

```
curl -X POST https://how.is/v1/domain/security-score -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### Blacklist status <a id="domain_blacklist"></a>

`POST /v1/domain/blacklist` · **$0.005** · MCP tool `domain_blacklist`

Whether the addresses behind a domain appear on spam and abuse blocklists.

- Input: `{"domain": {"type": "string", "maxLength": 253, "description": "A bare domain name, e.g. example.com"}}`
- Returns: data.data.ipResults[]: target, isListed, listingCount, totalChecked.
- Free sample: `{"domain": "example.com"}` — or `GET https://how.is/v1/domain/blacklist?domain=example.com`

```
curl -X POST https://how.is/v1/domain/blacklist -H "Authorization: Bearer $KEY" -d '{"domain": "example.com"}'
```

### IP intelligence <a id="ip_lookup"></a>

`POST /v1/ip` · **$0.005** · MCP tool `ip_lookup`

Network owner (ASN), company, geolocation, privacy flags and abuse contact for an IP address.

- Input: `{"ip": {"type": "string", "description": "An IPv4 or IPv6 address"}}`
- Returns: data.data: ip, hostname, city, region, country, asn, company, privacy, carrier, abuse.
- Free sample: `{"ip": "1.1.1.1"}` — or `GET https://how.is/v1/ip?ip=1.1.1.1`

```
curl -X POST https://how.is/v1/ip -H "Authorization: Bearer $KEY" -d '{"ip": "1.1.1.1"}'
```

### Site status <a id="site_status"></a>

`POST /v1/check/site` · **$0.005** · MCP tool `site_status`

Live check of one or more URLs: HTTP status, latency, DNS resolve time, resolved IP, TTL, nameservers, TLS days remaining.

- Input: `{"sites": {"type": "array", "maxItems": 10, "items": {"type": "string", "format": "uri"}, "description": "Up to 10 http(s):// URLs"}}`
- Returns: data keyed by URL; each entry has ok, code, ms, dns_ms, ip, ttl, ns, cert_days, server, ts.
- Free sample: `{"sites": ["https://example.com"]}` — or `GET https://how.is/v1/check/site?sites=https://example.com`

```
curl -X POST https://how.is/v1/check/site -H "Authorization: Bearer $KEY" -d '{"sites": ["https://example.com"]}'
```

### Page-change watch <a id="page_watch"></a>

`POST /v1/watch/page` · **$0.005** · MCP tool `page_watch`

Has this page changed since your last call, and how much? The first call stores a baseline; later calls return a text diff (2% threshold).

- Input: `{"url": {"type": "string", "format": "uri", "description": "An http(s):// URL on the public internet"}}`
- Returns: data with baseline, changed, change_pct, added, removed, last_change_ts.
- Free sample: `{"url": "https://example.com"}` — or `GET https://how.is/v1/watch/page?url=https://example.com`

```
curl -X POST https://how.is/v1/watch/page -H "Authorization: Bearer $KEY" -d '{"url": "https://example.com"}'
```

### Bot observatory feed <a id="bot_observatory"></a>

`POST /v1/bots` · **$0.050** · MCP tool `bot_observatory`

Partner tier of the dns.pizza bot observatory: every network (ASN), named crawler, TLS (JA4) fingerprint and firewall rule seen in the last 7 days, refreshed hourly. The public tier is free at https://dns.pizza/bots.

- Input: `{}`
- Returns: data.data: generated_at, networks, bots, fingerprints (the free tier at dns.pizza/bots shows the same shape without fingerprints).
- No free sample.

```
curl -X POST https://how.is/v1/bots -H "Authorization: Bearer $KEY" -d '{}'
```

## Bundle pricing

`profile` runs 8 checks for $0.025, versus
$0.040 bought separately. It is billed in full if at least one section comes back
(`data.errors` names the missing ones, `data.billing` restates it), and refunded only if every
section fails. Each profile also stores a free page-watch baseline for `https://<domain>`.

## Monitors <a id="monitors"></a>

Scheduled checks funded by a prepaid key; each run is charged at the check's price.

```
curl -X POST https://how.is/v1/monitors -H "Authorization: Bearer $KEY" \
  -d '{"type":"site","target":"https://example.com","interval_min":5}'
```

- `type`: `site` or `page`; `interval_min` ≥ 1
- `GET /v1/monitors` lists yours; `PATCH /v1/monitors/<id>` `{"enabled": false}` or `{"interval_min": 30}`;
  `DELETE /v1/monitors/<id>`
- Poll `GET /v1/monitors` for each monitor's state, last run and last summary. Alerts are not
  sent to customers yet. A cap or balance failure pauses the monitor.

## Account

- `GET /ledger` with your Bearer key: balance, today's spend, recent receipts.
- The daily spend cap (default $0.10) is raised by the operator on request.
