Skip to content

Verifying ownership

A website must prove domain control before its node is trusted. Verification is a challenge-and-check flow: the server issues a token, you place it on the domain, and the server re-reads it to advance a state machine. The method you use sets the node's trust tier.

Why verification gates everything

Verification is what makes a Knowledge Node a primary source rather than a claim. An AI consumer weights a node by how its domain ownership was proven. Verification gates ingestion and publishing: content should only be pushed when the website is verified (or in grace), and a node should never be made public on an unverified domain. The trust tier derived from the method is published in trust.json and mirrored into manifest.json.trust_tier.

The verification methods

The method drives the trust tier (highest to lowest):

MethodTrust tierHow you prove ownership
dns_txthighestAdd a TXT record containing the challenge token to the domain's DNS.
plugin_signedhighA signed plugin (for example the WordPress connector) attests ownership.
well_known_filemedium-highServe the token at a well-known path on the site.
meta_tagmedium-lowAdd a <meta> tag with the token to the site's HTML.
manuallowestA reviewed, manually-approved verification.

A higher-tier method produces a node an AI consumer can trust more. dns_txt is the strongest because it proves control of the domain itself, not just of one page.

The endpoints

apps/api/src/modules/verification/router.ts. The challenge fetch is SSRF-guarded.

MethodPathMin roleStatus
GET/v1/websites/:id/verificationviewer200 (current state)
POST/v1/websites/:id/verification/challengeeditor201 (issues a token)
POST/v1/websites/:id/verification/checkeditor200 (re-reads, advances state)

Errors: validation.failed, website.not_found, auth.forbidden, verification.method_unsupported.

The flow

1. Request a challenge

bash
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/verification/challenge \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{ "method": "dns_txt" }'

The response carries the token to place. The method determines where it goes.

2. Place the token

  • dns_txt: add a TXT record to the domain containing the challenge token.
  • well_known_file: serve the token at the well-known path returned by the challenge.
  • meta_tag: add the returned <meta> tag to the site's HTML <head>.
  • plugin: configure the signed connector plugin, which attests ownership.

3. Check

bash
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/verification/check \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{}'

The server re-reads ownership and advances the state machine. On success the state becomes verified.

Verification states

StateMeaning
pendingA challenge is issued but ownership is not yet confirmed.
verifiedOwnership is confirmed; the node is trusted and can publish.
gracePreviously verified, but a re-check is overdue or failing; still allowed to operate temporarily. Ingestion in this state carries a meta.warning="verification_grace".
failedA check failed; ownership could not be confirmed.
revokedVerification was withdrawn.
 challenge        check (ok)           re-check overdue/failing
pending ---------> verified ------------------------> grace
   ^                  |   ^                              |
   |  check (fail)    |   | re-check (ok)                | re-check (fail)
   +---- failed <-----+   +------------------------------+
                          revoked (withdrawn)

Periodic re-verification

A periodic re-verification job (verification.check) and verification.lost / verification.restored events are specified, but the worker handler is a stub today and no scheduler enqueues it on a timer. Re-checks are triggered through the check endpoint. See Architecture.

How verification feeds ingestion and publish

  • Ingestion should run only when the website is verified or grace. In grace, responses carry a meta.warning="verification_grace" hint.
  • Publish writes a trust posture into trust.json from the verification method and state. (The current build does not yet hard-gate publish with a 409 website.not_verified pre-check; that gate is deferred.)
  • The trust tier is mirrored into manifest.json.trust_tier so a consumer reads it from the node entry point.

Owner-controlled structured data for AI.