API documentation
Everything takes a key, answers in JSON, and uses plain HTTP, so it works from any language.
Get your API keyYour key
Send your key on every call. Keep it on your server: anyone with it can use your allowance.
curl "https://www.purlock.com/v1/tools/email-deliverability-checker?target=yourshop.com" \ -H "Authorization: Bearer pk_live_..."
A key can read, or read and write. To use facts, scans or prechecks, tie the key to a site when you create it.
Limits
| Plan | Credits a month | Calls a minute |
|---|---|---|
| Free | 100 | 20 |
| Individual | 5,000 | 120 |
| Business | 25,000 | 300 |
Most calls cost 1 credit, a precheck 5 and a full scan 20. Each answer has x-credits-used and x-credits-limit headers.
Errors
Errors always look the same, so your code can check the code and show the message.
{ "error": { "code": "out_of_credits", "message": "You've used this month's API calls." } }Tools
GET /v1/tools/{tool}?target=...
status is good, attention or bad. Each check has a level, title, detail and fix. data has the same answer in full.{
"tool": "email-deliverability",
"target": "yourshop.com",
"status": "attention",
"headline": "yourshop.com works, with 1 thing worth fixing.",
"checks": [{ "id": "spf-near-limit", "level": "warn", "title": "SPF uses 8 of 10 DNS lookups: close to the limit", "fix": "..." }],
"facts": [{ "label": "SPF lookups", "value": "8 of 10" }],
"data": { "spf": { "lookups": 8, "costs": [] } },
"checkedAt": "2026-11-05T09:00:00.000Z"
}- email-deliverability-checker
SPF lookups against the limit of 10, DMARC, DKIM and mail servers.
- website-health-check
SSL and domain expiry, DNS, https redirects and security headers.
- page-change-alerts
Watch any page and get an email or webhook when its text changes.
- url-to-markdown
Turn any web page into clean Markdown, for AI tools and notes.
- url-metadata
Title, description, image, favicon and site name from any address.
- email-checker
Spot typos, disposable addresses and domains that can't get email.
- redirect-checker
See every redirect a link goes through, and where it really ends.
- seo-checker
Title, description, headings, canonical, share tags and structured data.
- ai-crawler-checker
Which AI bots can read your site, and a draft llms.txt.
- tech-stack-checker
What a website is built and hosted with, from clear fingerprints only.
- favicon-finder
Find any site's favicon address, and check that it loads.
GET /v1/favicon?target=example.com&redirect=1
redirect=1 a redirect to it.Facts
The values behind Live Facts. Change one, and every page using its tag shows the new value within seconds.
GET /v1/facts
GET /v1/facts/{key}
PUT /v1/facts/{key}
curl -X PUT "https://www.purlock.com/v1/facts/pro-price" \
-H "Authorization: Bearer pk_live_..." \
-H "content-type: application/json" \
-d '{ "value": "$29/month" }'Prices
Link a fact to a Stripe lookup key on your Live facts page, and Stripe decides what it shows. With Stripe connected, changes arrive straight away. With a pasted key, we check every day and after every sale.
GET /v1/prices/{lookup_key}
{ "lookupKey": "pro_monthly", "amount": 2900, "currency": "usd", "interval": "month", "formatted": "$29/month", "lowest30Days": 1900, "lowest30DaysFormatted": "$19/month" }Plan status
What a customer is on right now, kept current by Stripe's webhooks, so it's fast enough to check on every request. Connect Stripe on your site's Live facts page first.
GET /v1/customers/{stripe_customer_id}/plan
limit_seats = 5 comes back as seats: 5. Someone with no subscription gets active: false, not an error. Cache the answer, and decide what your app does if we can't be reached.{
"customer": "cus_123",
"active": true,
"status": "active",
"plan": { "name": "Pro", "lookupKey": "pro_monthly", "priceId": "price_123" },
"limits": { "seats": 5, "sso": true },
"renewsAt": "2026-12-05T00:00:00.000Z",
"endsAt": null,
"trialEndsAt": null
}Scans and prechecks
These come with the Individual and Business plans, on a verified site.
POST /v1/scan
GET /v1/scan/{id}
POST /v1/precheck
{ "text": "..." } or { "url": "https://preview..." }.Webhooks
Add one on your API keys page. We send finding.created, finding.resolved and fact.changed for a site, and monitor.changed for monitors. Check each one is really from us:
import { createHmac, timingSafeEqual } from "node:crypto";
function isFromPurlock(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // Too old: a replay.
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Live Facts in React
Live Facts needs no key: its values are public. Keep the current value inside the tag, so visitors still see it if the script can't run.
<script src="https://www.purlock.com/live/v1.js" data-site="YOUR_SITE_ID" async></script> <span data-purlock="pro-price" suppressHydrationWarning>$19/month</span>