Stack Scan
Detect a website's technology stack with POST /v1/stack/scan.
POST https://api.jobspipe.dev/v1/stack/scanGiven a domain, returns the technologies it serves - frontend frameworks, analytics, CDNs, payment processors, and SaaS widgets - detected from its live homepage. Results are cached, and a domain's history keeps a new entry only when its stack changes. Requires authentication.
Prefer to explore interactively? The API Explorer has a live playground with the request body, the full response schema, and a "try it" panel.
Request
The body is a JSON object with a single required domain.
| Field | Type | Description |
|---|---|---|
domain | string | Required. The domain to scan, e.g. stripe.com. URLs and a leading www. are normalized off. |
mode | string | Optional. auto (default) tries a fast HTTP fetch, then a headless render if the results are thin. |
curl https://api.jobspipe.dev/v1/stack/scan \
-H "Authorization: Bearer jp_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "domain": "stripe.com" }'import requests
resp = requests.post(
"https://api.jobspipe.dev/v1/stack/scan",
headers={"Authorization": "Bearer jp_live_your_key_here"},
json={"domain": "stripe.com"},
)
resp.raise_for_status()
result = resp.json()
for tech in result["detected"]:
print(tech["name"], "-", ", ".join(tech["categories"]))const resp = await fetch("https://api.jobspipe.dev/v1/stack/scan", {
method: "POST",
headers: {
Authorization: "Bearer jp_live_your_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ domain: "stripe.com" }),
});
const result = await resp.json();
for (const tech of result.detected) {
console.log(tech.name, "-", tech.categories.join(", "));
}Response
A 200 response describes the domain's current stack:
detected- an array of detected technologies. Each hasname,slug,categories, aconfidencescore (0-100), an optionalversion, and metadata such aswebsite,saas, andoss.scanned_at- when this stack snapshot was recorded. A new snapshot is stored only when the detected stack changes, so this is effectively "the stack has been this way since".http_status/render_path- how the domain responded and which fetch path produced the result.
{
"domain": "stripe.com",
"scanned_at": "2026-06-25T05:56:00Z",
"http_status": 200,
"render_path": "curl_cffi",
"detected": [
{
"slug": "react",
"name": "React",
"categories": ["JavaScript frameworks"],
"confidence": 100,
"version": null,
"website": "https://react.dev",
"saas": false,
"oss": true
},
{
"slug": "cloudflare",
"name": "Cloudflare",
"categories": ["CDN"],
"confidence": 100,
"version": null,
"website": "https://www.cloudflare.com",
"saas": true,
"oss": false
}
]
}A reachable domain with no recognizable technologies returns http_status: 200 and an empty detected array. A domain the scanner could not reach returns http_status: 0.
Each call counts as one request against your plan's monthly quota and per-second rate limit. A repeat scan of the same domain within the freshness window is served from cache and still counts as one request. See plans.
Status codes
| Status | Meaning |
|---|---|
200 | Success |
400 | Missing or invalid domain |
401 | Missing or invalid API key |
402 | Monthly request quota exceeded |
429 | Per-second rate limit exceeded |
502 | The scanner failed |
504 | The scan timed out |
See the error reference for handling guidance.