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