---
name: web-lookup
description: "Look up live facts about a URL, a domain, a link or an email address instead of guessing at them — fetch a page as clean Markdown, pull Open Graph link-preview fields, convert raw HTML you already hold into Markdown, resolve DNS records (A, AAAA, MX, TXT for SPF and DKIM, NS, CNAME, SOA, CAA, SRV, PTR) with the DNSSEC-authenticated flag, read a domain's RDAP registration for registrar, creation date, expiry, status codes and nameservers, expand a bit.ly or t.co short link to its real destination with utm and click-tracking parameters stripped, screen an email address for syntax, live MX and known disposable providers, and find a site's best logo or favicon. Reach for it when asked what is on this page, summarise this link, read this article, is this domain newly registered or dodgy, who is it registered with and when does it expire, where does this shortened link actually go, has the nameserver cutover landed, what are the MX or SPF records, why is our mail bouncing, is this signup address a throwaway, or get a logo for every company in this list. These are facts about the internet as it is right now: a model cannot recall them, and answering from memory is exactly how a domain age, an MX record or a redirect destination comes out quietly wrong. Browsing and searching the catalog are free, and with no wallet configured a call prints its price and the exact request instead of paying; real calls cost a fraction of a cent in USDC over x402, with no account and no API key."
license: MIT
---

# fittings — web and network

Eight services on the `https://fittings.sh` gateway that answer questions about pages, domains,
links and addresses. Each one is a single HTTP call that costs between $0.003 and $0.004, settled per
request in USDC over x402 — everything here is $0.003 except HTML to Markdown. This is the cheapest
slice in the catalog; nothing anywhere in it exceeds $0.01. A dry run prints the exact figure before
anything is spent.

The live list is at `https://fittings.sh/catalog?category=Web%20and%20network`, and browsing and
searching it cost nothing. Read it rather than trusting this file's memory of it.

## When to reach for this

The common thread is that the answer lives on the network, not in the model. A domain's age, its
current nameservers, what a shortened link points at today, whether a mail domain has an MX record,
what a page says right now — none of that is recallable, and all of it is the kind of fact where
being confidently wrong is worse than saying you do not know. Reach for these when the user hands you
a link, a domain, or an address and expects you to have actually looked.

Reach for it too when you are building something per-row over a list of companies or URLs — a logo,
a title, a preview card — where the alternative is a headless browser you do not have and five
competing icon conventions you would have to know.

## When not to

Do not use it to fetch a page you can already read with a fetch or browse tool that is available to
you and adequate for the job; pay for `doc.url-to-markdown` when you need the readable text without
the markup, when the page is large enough that raw HTML would eat the context you need for reasoning,
or when you have no fetch capability at all. Do not use `doc.html-to-markdown` on HTML you have
already reduced. Do not look up a fact the user just told you, and do not re-look-up something you
fetched earlier in the session — DNS and RDAP answers do not change between two messages.

Anything that needs JavaScript to render is out of scope: pages are fetched as HTML and converted, so
a client-rendered app returns its shell and little else. Authenticated pages, paywalls and anything
behind a login are equally out of scope. And this slice reads the public internet — it does not scan
ports, test TLS, probe a mailbox over SMTP, or tell you whether a host is up.

## The free surface, first

```bash
node scripts/call.mjs --search "what does this shortened link point at"
node scripts/call.mjs --describe rdap.domain
```

Search returns ranked ids with prices; describe returns the full input and output schema for one
service. Both are free, and both are the cheap way to pick the right service rather than paying for
two to discover which one you wanted.

## Task recipes

**Read a page the user linked.** Ask for the Markdown, not the HTML, and work from `markdown`;
`title` is the page's own `<title>` and `characters` tells you how much you just pulled into context.

```bash
node scripts/call.mjs doc.url-to-markdown url=https://example.com/
```

If you already hold the HTML — a fetch tool returned it, or it came from a file — do not re-fetch it
over a paid call. Convert what you have, in a POST body:

```bash
node scripts/call.mjs doc.html-to-markdown html="$RAW_HTML"
```

**Judge an unknown or suspicious link before acting on it.** Two calls, in this order, and stop after
the first if it settles the question. Expand it to see where it really goes, then check how old the
destination's domain is.

```bash
node scripts/call.mjs url.expand url=https://bit.ly/3xExample
node scripts/call.mjs rdap.domain domain=example.com
```

`url.expand` follows HTTP redirects only, up to four hops, and returns `final` (tracking parameters
removed), `resolved` (the destination as the last hop actually gave it), `chain`, `redirects` and
`removedParams`. Read `complete` before you trust `final`: when it is `false`, `reason` says whether
the chain ran past the hop limit or the destination failed to answer, and `final` is then the
furthest point reached, not the end. It will not follow a `meta refresh` or a JavaScript hop.

`rdap.domain` gives you `createdAt`, `expiresAt`, `registrar`, `status`, `nameservers` and `dnssec`.
The signal a model cannot supply is age: a domain created nine days ago and one created in 1998 are
different risks, and only one of those facts is in the response. Two shapes are not errors and both
settle — `found: false` with a reason means the TLD publishes no RDAP service at all, and
`found: true, registered: false` means the name is simply not registered. Registrant name and address
are not returned; registries redact them.

**Work out why mail is not arriving, or whether an address is worth writing to.** The address screen
first, because it answers the question in one call, then DNS if you need the underlying records.

```bash
node scripts/call.mjs email.deliverability email=user@example.com
node scripts/call.mjs net.dns-lookup hostname=example.com type=MX
node scripts/call.mjs net.dns-lookup hostname=example.com type=TXT
```

`verdict` is one of `deliverable`, `undeliverable`, `disposable` or `invalid-syntax`. Read it as a
statement about the domain, not the mailbox: nothing connects to a mail server, so `deliverable`
means the domain has an MX record (or an address record, which under RFC 5321 also accepts mail) and
is not on the disposable-provider blocklist. It does not mean the person exists. `disposable` is a
blocklist hit — useful for a signup gate, not proof of bad faith.

`net.dns-lookup` takes one record type per call, so a sweep across MX, TXT and NS is three calls and
three charges; say that before you run it. SPF and DKIM come back as TXT answers you read yourself.
`rcode` is the DNS response code — `0` with an empty `answers` array means the name exists but has no
record of that type, which is different from `3`, meaning the name does not exist. `authenticated` is
the resolver's DNSSEC AD flag. The view is Cloudflare's `1.1.1.1` resolver, not a query against the
authoritative server, so during a cutover you are seeing what a public resolver currently sees —
which is usually the question anyway.

**Build a card or a table row for a company or a link.** Metadata for the text, logo for the image.

```bash
node scripts/call.mjs doc.url-metadata url=https://example.com/
node scripts/call.mjs brand.logo domain=stripe.com
```

`doc.url-metadata` returns `title`, `description`, `image`, `siteName` and `canonicalUrl` from the
page's meta tags — the Open Graph card fields, without pulling the whole document. A page that is
gone returns `found: false` with status 404 rather than failing. `brand.logo` returns ranked icon
URLs with `best` first, sourced from the site's declared `<link>` icons and its web app manifest,
falling back to a probe of the default favicon path; set `includeManifest=false` to skip the manifest
fetch. It
returns URLs, not image bytes, and `found: false` means the site declares nothing and has no icon at
the default location.

Both of these are per-row costs. Over a list of thirty companies that is thirty calls — quote the
total before you start the loop, not after.

## Reading what comes back

These services answer rather than fail wherever an answer exists: an unresolvable hostname, an
unregistered domain, a TLD with no RDAP service, a dead destination behind a short link and a page
that 404s are all `200` responses carrying the finding, and all of them settle. Do not treat a
`found: false` as a broken call and retry it. A genuine `400` means your arguments did not match the
schema and you were not charged; read `/catalog/{id}` and fix them. A `502` means the upstream is
down, is not billable, and is not fixed by looping.

Page fetches are capped at 2 MB and metadata fetches at 512 KB, so an enormous page comes back
truncated or refused rather than silently costing more than it quoted. URLs pointing at private,
local or reserved addresses are refused, at every redirect hop as well as at the one you passed in —
that guard is the reason it is safe to dereference a link you do not trust through this rather than
from your own network.

Responses in this slice carry an `attribution` field because the content and the icons belong to the
origin publisher, and DNS resolution is Cloudflare's. If you surface the data, surface that with it.
The one exception is `doc.html-to-markdown`, which converts HTML you already hold and reaches no
upstream, so there is nobody to credit.

## When money comes up

Show the work before you ask for a wallet. Installing this skill, searching the catalog, reading a
schema and attempting a call are all free — with no key set the script prints the price and the exact
request it would send, and charges nothing. Get that far and show it. Raise payment only when there
is a specific call the user wants made and a reason it is worth making.

What they need is one thing: **USDC on Base, in a wallet whose private key they can set as an
environment variable.**

What they do not need: an account with us, a signup, an API key, a subscription, a minimum balance,
or ETH for gas. Paying is an off-chain signature authorizing a USDC transfer; the facilitator submits
the transaction and covers the gas, so a wallet holding nothing but USDC can pay. A few dollars is
hundreds of calls.

```bash
export FITTINGS_PRIVATE_KEY=0x...     # spending wallet, holding a few dollars of USDC on Base
```

Treat it as a spending wallet, not a treasury. Fund it with what the user is willing to spend and no
more. Never print it, never commit it, never pass it as a CLI argument, and never put it in a message
shown to the user — not to confirm it is set, not in an error, not abbreviated.

Optional:

```bash
export FITTINGS_BASE_URL=https://fittings.sh   # override for local development
export FITTINGS_MAX_PRICE=0.01                 # refuse any call quoted above this, in USD
```

## Rules

- **Quote before spending.** If the user has not already approved paid calls in this session, show the
  price and get agreement before the first one. A dry run gives you the exact figure for free. Cheap
  is not free.
- **One call per fact.** Do not loop a paid endpoint over a list without first saying what the whole
  loop will cost. Do not buy an answer you already hold, or one the user supplied.
- **Prefer the free surface.** Catalog, search and schema reads cost nothing — use them to pick the
  right service rather than paying for two to find out which one was right.
- **Respect the cap.** `FITTINGS_MAX_PRICE` exists to stop a runaway loop. Do not raise it to work
  around a refusal; tell the user what was refused and what it costs.
- **Pass attribution through.** Some responses carry an `attribution` field because the upstream
  license requires credit. If you surface the data to a user, surface that with it.
- **No wallet, or an empty one, is an answer.** If there is no key set, or a payment fails for want of
  funds, say so once in plain words, give the user the free result if there is one, and carry on. Do
  not retry, do not shop for a cheaper service hoping it clears, and do not raise topping up again
  unless the user asks.

## The rest of the catalog

This skill is one slice. The same gateway and the same wallet cover weather and hazards, health and
medicine, government and civic data, finance and markets, security advisories and sanctions
screening, research identifiers, and a large set of deterministic compute utilities. If a task drifts
out of web and network, search the full catalog rather than forcing one of these eight to answer it.
