Assets
Registering, listing, verifying and deleting assets through the partner API.
An asset is a domain, IP address, CIDR range or URL registered to the
organization, against which ownership is proven. An engagement is refused at
submit unless each of its targets and in-scope entries is covered by an asset
whose status is verified or attested.
Ownership verification describes the
methods, coverage and the launch re-check; this page describes the endpoints.
| Method | Path | Success |
|---|---|---|
POST |
/api/v1/assets |
201 and the asset |
GET |
/api/v1/assets |
200 and a list of assets, sorted by value |
DELETE |
/api/v1/assets/{asset_id} |
204, no body |
POST |
/api/v1/assets/{asset_id}/verify |
200, the outcome and the asset |
GET |
/api/v1/assets/{asset_id}/verifications |
200 and the verification log, newest first |
An asset that belongs to another organization is answered
404 Asset not found, the same as one that does not exist.
Registering an asset¶
curl -X POST https://<pentr-host>/api/v1/assets \
-H "X-API-Key: $PENTR_API_KEY" -H "Content-Type: application/json" \
-d '{"kind": "domain", "value": "Example.COM", "include_subdomains": true}'
| Field | Type | Required | Notes |
|---|---|---|---|
kind |
string | yes | domain, ip, cidr or url |
value |
string | yes | Normalised before it is stored; see below |
include_subdomains |
boolean | no, default false |
Stored as false for any kind but domain |
parent_asset_id |
UUID | no | Must name an asset in the same organization |
The value is normalised for its kind: a domain is lower-cased, loses a trailing
dot and is IDNA-encoded; an IP address and a CIDR range are written in canonical
form, with host bits cleared from the range; a URL must be http or https
and keeps its scheme, host, a non-default port and its path without a trailing
slash, dropping any query or fragment. 10.0.0.1/24 is stored as
10.0.0.0/24, and HTTPS://App.Example.com:443/admin/ as
https://app.example.com/admin. The normalised value is stored in a column
of 255 characters. A value one character over the limit is answered with a
plain-text 500.
The response is the asset:
{
"id": "bbc5c5e5-9a65-452b-b26f-616f9e46c011",
"kind": "domain",
"value": "example.com",
"include_subdomains": true,
"parent_asset_id": null,
"status": "unverified",
"verified_method": null,
"verified_at": null,
"expires_at": null,
"verification_token": "pentr-verify-383bfadebd6ba0b46aefe39105502d27",
"dns_record_name": "_pentr-verify.example.com",
"dns_record_value": "pentr-verify-383bfadebd6ba0b46aefe39105502d27",
"well_known_url": "https://example.com/.well-known/pentr-verification.txt",
"created_at": "2026-09-23T09:36:57.140800"
}
| Field | Meaning |
|---|---|
status |
unverified, verified, attested or revoked |
verified_method |
The method that proved the asset: dns_txt, well_known or attestation; null until one has |
verified_at |
When it was proved; null until then |
expires_at |
Set for an attestation, which lapses after 90 days; null otherwise |
verification_token |
The token to publish; it does not change for the asset's lifetime |
dns_record_name |
The TXT record name to publish. Non-null for domain assets only |
dns_record_value |
The TXT record value, equal to verification_token, for every kind |
well_known_url |
The URL at which to serve the token. Non-null for domain assets only |
For an ip, cidr or url asset, dns_record_name and well_known_url are
null: no automated method applies to those kinds.
Errors on create:
| Status | detail |
Cause |
|---|---|---|
422 |
a list | The body fails validation, for example an unknown kind (kind must be one of: cidr, domain, ip, url) or a missing field |
422 |
a string | The value is not valid for its kind, for example not a valid domain: 'nodot' or '10.0.0.1' is an IP address, not a domain |
422 |
a string | parent_asset_id does not name an asset in this organization's inventory |
409 |
a string | The organization already holds an asset of this kind with this normalised value, for example example.com is already in this organization's inventory |
Verifying an asset¶
POST /api/v1/assets/{asset_id}/verify takes no body. For a domain asset it
checks the DNS TXT record first and the well-known file second, stops at the
first pass, and records every attempt in the asset's log. A pass sets the asset
to verified:
{
"outcome": "pass",
"asset": {
"id": "bbc5c5e5-9a65-452b-b26f-616f9e46c011",
"kind": "domain",
"value": "example.com",
"status": "verified",
"verified_method": "well_known",
"verified_at": "2026-09-23T09:36:59.456362",
"expires_at": null
}
}
(The asset object is the full asset shown above; it is trimmed here.)
When neither check passes, the answer is the outcome of the last check that
ran, and detail is an object:
| Status | detail |
Meaning |
|---|---|---|
422 |
{"outcome": "fail", "reason": …} |
The record or file is absent or wrong |
503 |
{"outcome": "error", "reason": …} |
The lookup itself failed, for example a DNS timeout or a connection error |
422 |
"no verification method applies to a ip asset" |
The asset is not a domain; nothing is attempted or recorded |
{
"detail": {
"outcome": "fail",
"reason": "https://example.com/.well-known/pentr-verification.txt returned HTTP 404, expected 200"
}
}
A failed verify does not change the asset's status. An asset that is already
verified stays verified after a failed check; the failure is recorded in
its log.
The partner API has no attestation endpoint. An ip, cidr or url asset can
be proven only by an attestation: a request the organization's owner makes with
their console sign-in session, not with an API key. See
Ownership verification.
The verification log¶
GET /api/v1/assets/{asset_id}/verifications returns every recorded attempt,
newest first, including the attempts the launch gate makes when an engagement
is submitted:
[
{
"id": "c8ad2a5a-ab44-4987-8c67-2678d8bbe218",
"method": "well_known",
"outcome": "pass",
"reason": null,
"evidence_sha256": "7e087287b28f970b9014ee7400d34a98d25529a074b8dfa860903ffd69b62c7e",
"checked_at": "2026-09-23T09:36:59.455626",
"trigger": "manual",
"engagement_id": null
}
]
trigger is launch for an attempt made by the re-check at submit and manual
for any other; engagement_id names the engagement a launch attempt gated. The log's
evidence itself is not returned, only its SHA-256 digest.
Deleting an asset¶
DELETE /api/v1/assets/{asset_id} deletes an asset that has no recorded
verification attempts and answers 204. An asset with any attempt in its log,
passed or failed, cannot be deleted:
{
"detail": "example.com has verification history and cannot be deleted; its attempt log is the audit trail for engagements it authorized"
}
That answer is 409. The partner API has no other way to retire an asset.
Last updated 2026-09-24