/v1/links
Follows a URL's redirect chain and screens where it actually ends up.
- METHOD
- POST
- BODY CAP
- 4 KiB
- CHECKS
- 3
- FINDING CODES
- 3
POST /v1/links screens one URL. It’s for links a user pasted or clicked through — a
“visit my website” field, a link shared in a message you’re relaying, a URL from a form —
where what matters is not the URL string itself but where it actually leads once every
redirect resolves.
Request#
{ "url": "https://example.com" }
| Field | Type | Required | Notes |
|---|---|---|---|
url |
string | yes | 1–2048 characters. Not pre-validated beyond length — an unparseable string simply produces no chain data and no chain-related findings. |
There are no optional fields yet.
Request body cap: 4 KB — far more than a single URL needs, with headroom for future optional fields.
Checks this gate runs#
link.redirect_chain and link.destination_reputation make network calls (following
redirects, then an RDAP lookup on the final destination); link.shortener is pure
computation on the submitted URL string itself.
| Check | What it does |
|---|---|
link.redirect_chain |
Follows redirects (up to 10 hops), refusing any hop that targets a non-public host — see link.hop_cap_exceeded, link.shortener_chain, link.unsafe_redirect_target. |
link.destination_reputation |
Runs registration-age and typosquat checks against the chain’s final destination domain — see domain.not_registered, domain.newly_registered, domain.typosquat (owned by /domains, reused here against the destination rather than the submitted host). |
link.shortener |
Flags whether the submitted URL itself is on a known shortener host. Contributes data.shortener.service; emits no finding of its own. |
This gate does no content scanning of the destination page — no malware signatures, no page-content inspection. Detection here is entirely about the chain’s structure (hop count, shortener services involved, whether any hop was refused) and the final domain’s registration signals.
Example#
curl -s -X POST https://api.stormdoor.dev/v1/links \
-H "Authorization: Bearer sk_live_..." \
-H "content-type: application/json" \
-d '{"url":"https://example.com"}'
{
"ok": true,
"verdict": "clean",
"findings": [],
"data": {
"chain": { "hops": [{ "url": "https://example.com", "status": 200 }], "finalUrl": "https://example.com", "truncated": false },
"destination": { "domain": "example.com", "rdap": { "createdAt": "1995-08-14T04:00:00.000Z", "ageDays": 11367 } }
},
"checks_run": ["link.destination_reputation", "link.redirect_chain", "link.shortener"],
"checks_skipped": [],
"meta": { "gate": "links", "ruleset": "reference-9f0e0204" }
}
Real response — a direct, single-hop link to a long-registered domain. data.destination
is only populated by link.destination_reputation; if that check is skipped for any reason,
the field is absent, not null (design rule 7).
See response format for checks_skipped reasons, and don’t proxy that
array to your own end users.
Finding codes#
Each code links to its own page with what it looks at, why it matters and what to do.