Technographics (beta)
Detect the technologies a website and its DNS show, with evidence and an assessment for each finding, from one domain.
Beta
Technographics is in beta for Scale and Custom workspaces. tech_id is the upstream catalogue slug for now and may change during the beta, when the Ploid
technology catalogue lands. During the beta, name and category also come from the upstream catalogue and can change with it, and the evidence
source_scope values may change. Other fields may be added; existing fields keep their meaning.
POST /v1/technographicsSend one domain and get back the technologies Ploid found on its website and in its DNS: frameworks, analytics, hosting, email providers, verification tokens and more. Every finding carries an assessment of how strong its evidence is. Direct findings cite the page, header or DNS record they came from; inferred findings name the technology that implies them.
Scans are static: Ploid reads the homepage and a few same-site pages over HTTP, plus the domain's public DNS records. A scan usually takes about 10
seconds and stops at 25. Pages are not rendered in a browser yet, so technologies that only appear after JavaScript runs can be missed. Ploid honors
robots.txt, never logs in, and does not submit forms.
Requires an API key with the people:search scope. Older full-access keys that hold people:enrich and agent:chat also work. You can also try it in
the API playground.
curl https://api.ploid.com/v1/technographics \
-H "Authorization: Bearer $PLOID_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"domain": "acme.com",
"freshness": { "max_age_days": 30 }
}'Request
| Field | Meaning |
|---|---|
domain | The website's domain, such as acme.com or www.acme.com. Paths, IP addresses and internal hosts are rejected with 422 invalid_domain. |
freshness.max_age_days | Optional integer, 0 to 365 (default 30). The oldest stored scan to reuse. 0 asks for a fresh scan; see Freshness and caching below. |
Response
{
"data": {
"domain": "acme.com",
"canonical_domain": null,
"status": "assessed",
"source": "scan",
"scanned_at": "2026-10-20T15:04:05.000Z",
"technologies": [
{
"tech_id": "next-js",
"name": "Next.js",
"category": "javascript-frameworks",
"categories": ["javascript-frameworks", "web-frameworks"],
"scope": "website",
"assessment": "observed",
"version": "14.2.3",
"channels": ["static"],
"first_observed_at": "2026-10-20T15:04:06.000Z",
"last_observed_at": "2026-10-20T15:04:07.000Z",
"inferred_from": [],
"evidence": [
{
"source_url": "https://acme.com/",
"channel": "static",
"source_scope": "website_delivery",
"signal": "headers:x-powered-by",
"match": "Next.js 14.2.3",
"rule_id": "9c1f...",
"artifact_sha256": "4b2e...",
"observed_at": "2026-10-20T15:04:06.000Z",
"version": "14.2.3"
}
]
},
{
"tech_id": "react",
"name": "React",
"category": "javascript-frameworks",
"categories": ["javascript-frameworks"],
"scope": "website",
"assessment": "inferred",
"version": null,
"channels": [],
"first_observed_at": null,
"last_observed_at": null,
"inferred_from": ["next-js"],
"evidence": []
},
{
"tech_id": "google-workspace",
"name": "Google Workspace",
"category": "email",
"categories": ["email"],
"scope": "email_routing",
"assessment": "observed",
"version": null,
"channels": ["dns"],
"first_observed_at": "2026-10-20T15:04:05.000Z",
"last_observed_at": "2026-10-20T15:04:05.000Z",
"inferred_from": [],
"evidence": [
{
"source_url": "https://acme.com/",
"channel": "dns",
"source_scope": "email_routing",
"signal": "dns:MX",
"match": "aspmx.l.google.com",
"rule_id": "51ad...",
"artifact_sha256": "e07c...",
"observed_at": "2026-10-20T15:04:05.000Z",
"version": null
}
]
}
],
"coverage": { "pages": 3, "dns": true, "limits": [] },
"versions": { "collector": 5, "rules": "...", "assessment": "2026-09-28.1", "catalog": "upstream-..." }
},
"meta": {
"request_id": "req_...",
"credits_charged": 1,
"remaining_credits": 3999,
"freshness": { "max_age_days": 30 }
}
}| Field | Meaning |
|---|---|
domain | The normalized domain you asked for. |
canonical_domain | Where the homepage redirected, when that differs from domain; otherwise null. |
status | assessed, partial, blocked, or failed; see below. |
source | scan for a scan run for this request, cache for a stored scan (see Freshness and caching). |
scanned_at | When the scan behind this result started. |
technologies | One record per technology and scope, at most 300. |
coverage | pages captured, whether dns answered, and limits: plain-language notes on anything that cut or capped the scan. |
versions | The collector, rule, assessment and catalogue revisions that produced the result, for comparing results over time. |
Each technology record has a name, its first upstream category and all categories, a scope and an assessment, a version when every sighting
in that scope agrees, the channels it was seen on (static or dns; browser is reserved for a future rendered scan), and when it was first and
last seen. A record keeps up to five evidence items, and very large results can carry fewer; inferred records have none. Each item names the
source_url (query strings, fragments and credentials removed), the channel and observed_at of the sighting, the signal that matched (such as
scriptSrc, headers:server or dns:MX), a match excerpt of up to 256 characters, a version when the sighting carried one, and the rule_id and
artifact_sha256 of the rule and input that matched. source_scope is the scanner's finer-grained scope (such as website_delivery or dns_alias);
its values are beta and may change.
Status
status | Meaning |
|---|---|
assessed | Collection finished within its budgets. |
partial | Something was captured, but the deadline or a budget cut coverage, or only DNS answered. |
blocked | No page was captured: robots.txt disallowed it or answered 429 or 5xx, the page answered 401 or 403, or the domain opted out of crawling by Ploid. |
failed | Nothing usable came back: no pages and no DNS records. |
An empty technologies list is not proof that a site uses nothing; read coverage.limits and status before drawing conclusions. A result with no
page captured can still list website technologies seen in the headers or cookies of an error or refusal response, such as the CDN in front of a
homepage that answered 502 or 403.
Scopes and assessments
A technology can appear once per scope, assessed only on that scope's evidence. A vendor in both an SPF record and a website script is two records:
configured under email_authorization and observed under website.
scope | Where it was seen |
|---|---|
website | Pages, response headers, scripts, stylesheets and other resources the site serves. |
email_routing | MX records: who receives the domain's email. |
email_authorization | SPF records: who may send email for the domain. |
domain_verification | Verification tokens published in the domain's DNS TXT records. |
dns_hosting | NS and alias records: who hosts the domain's DNS or fronts its hostnames. |
certificate | The issuer of the site's TLS certificate. |
website records are not all use: a Content-Security-Policy entry is configured, a verification meta tag on a page is verification_only, and a
product named in page text is a content_candidate. Filter on assessment, not scope, when you want the technologies a site uses.
assessment | Meaning |
|---|---|
observed | Seen in the site's own code, headers or DNS. The strongest signal. |
configured | Referenced or authorized, such as an SPF include or a Content-Security-Policy entry. Not proof the product is in use. |
verification_only | Only a verification token. The domain once proved ownership to the service; that says little about current use. |
content_candidate | Named in page content, such as a blog post or logo wall. Review before claiming the company uses it. |
inferred | Implied by an observed technology (Next.js implies React). inferred_from lists the parents; there is no evidence. |
A match means a public signal matched a fingerprint. It is not a purchase record and does not prove company-wide adoption.
Freshness and caching
Ploid keeps each scan and serves the newest assessed or partial scan of the domain that is no older than freshness.max_age_days (default 30).
A blocked result is reused for up to 24 hours and a failed one for up to 10 minutes. Otherwise Ploid scans the site and returns source: "scan".
Stored results come back with source: "cache". meta.freshness echoes the window applied.
max_age_days: 0 asks for a fresh scan, with one exception: a domain scanned in the last 10 minutes is served from that scan. This minimum rescan
interval protects the sites being scanned, so repeated requests inside it return the same result, with source: "cache".
Once a website opts out of crawling by Ploid, its stored scans are no longer served, whatever they found (empty, blocked and failed results
too) and however recent, even from the last 10 minutes. That holds when the domain, the site its homepage redirected to, or a page or host a stored
scan's evidence cites opts out, whenever that was, and when any other host or page of the website opts out after the scan started. When the domain
itself opted out, its next request runs as a live scan that returns status: "blocked", source: "scan", no technologies and the coverage note
"domain opted out", free and without contacting the site; later requests get that answer from the cache under the blocked rule above. When only a
page or host opted out, the next request runs a normal live scan that leaves it out, and for a few seconds after the opt-out each request for that
website scans it again. If Ploid cannot check its opt-out list within 3 seconds before serving a stored scan, the request fails with
503 technographics_unavailable, retryable and free, without scanning.
Live scans are limited per workspace to 2 at a time and 200 started in any rolling hour; /v1/openapi.json publishes the limits in force. Over
either, the request fails with 429 scan_capacity_exceeded, a Retry-After header and error.retry_after_seconds; cached results are not limited
this way. A live scan that fails on Ploid's side with a retryable 503, and the blocked answer for a domain that opted out, do not count toward
the hourly quota.
Pricing
A billable result costs 1 credit per domain, whether it came from a fresh scan or the cache. A result is billable only when at least one page of the
website was captured (coverage.pages above 0) and it has at least one record that is not inferred with scope website (a technology seen on the
site itself). Results with no page captured are free, whatever records they carry: DNS-only results (email_routing, email_authorization,
domain_verification, dns_hosting, certificate), and results whose website records come only from an error or refusal response. blocked,
failed and empty results are free too.
meta.credits_charged shows what was charged. Enterprise contracts can set a different per-domain price.
Every request needs one domain's price available before it runs, whether or not its result turns out to be billable and whether or not it comes from
the cache: in the workspace balance, else 402 insufficient_acu, and within the API key's daily and monthly budgets, else
402 daily_budget_exceeded or monthly_budget_exceeded. The hold on the key's budget is released when the result is free.
Plans
Technographics is included on the Scale and Custom plans. Workspaces on other plans, such as Growth, API Pro, pay as you go
and the free trial, receive 403 plan_upgrade_required.
Errors
No error is charged. The one exception is 503 billing_unavailable: Ploid had the result but could not confirm whether its charge went through.
| Condition | Status and code |
|---|---|
| Missing or invalid credential | 401 missing_authentication, invalid_api_key, expired_api_key, revoked_api_key |
| The workspace cannot currently use the API | 402 active_account_required |
| Not enough credits, or the key's daily or monthly budget hit | 402 insufficient_acu, daily_budget_exceeded, monthly_budget_exceeded |
The key lacks people:search | 403 insufficient_scope |
| The workspace plan does not include Technographics | 403 plan_upgrade_required |
The Idempotency-Key was used for a different request, or its first request is still running | 409 idempotency_conflict, idempotency_in_progress |
| Invalid request body | 422 validation_failed |
| Not a public website domain | 422 invalid_domain |
| Request rate limit | 429 rate_limited |
| The workspace reached its live-scan limit (2 at a time or 200 an hour) | 429 scan_capacity_exceeded |
| Scanners are busy or unavailable | 503 technographics_busy, 503 technographics_unavailable |
| The scan failed in a way a retry would repeat | 503 technographics_scan_failed |
| The charge could not be confirmed | 503 billing_unavailable |
429 responses carry Retry-After, and 503 technographics_busy and technographics_unavailable carry retryable: true; retry them with bounded
backoff. 503 billing_unavailable carries retryable: false: do not retry it under a new Idempotency-Key, which could charge twice; contact
support with its request_id. Retrying with the same key replays that answer. 503 technographics_scan_failed also carries retryable: false: the
scan failed on Ploid's side in a way that would repeat for this domain, so contact support with its request_id instead of retrying; it counts
toward the hourly live-scan quota. 403 plan_upgrade_required and 429 scan_capacity_exceeded do not
use up the Idempotency-Key, so the same key works after an upgrade or a wait. See Errors & rate limits.
