Skip to content

Domain claim & verify

When you register a resource on 402 Index, it lands as pending-review — accepted, but not searchable until 402 Index approves it. Verifying your domain is the instant-approval path: prove you control the host, and every pending listing on that domain becomes findable at once.

It’s two steps, no auth, no signature, no payment. You claimDomain a host (402 Index issues a token), serve a small proof file, then verifyDomain so 402 Index re-fetches and approves. Both calls move no funds and never throw — a transport or HTTP error comes back as { ok: false, detail }.

client.claimDomain(urlOrDomain) claims the host of whatever you pass — a full resource URL or a bare domain both resolve to the hostname. On success it returns the proof to serve.

const claim = await client.claimDomain('https://api.example.com/report')
// → {
// ok: true,
// domain: 'api.example.com',
// verificationHash: '…', // serve THIS as the entire body of verificationUrl
// verificationToken: '…',
// verificationUrl: 'https://api.example.com/.well-known/402index-verify.txt',
// }
if (!claim.ok) {
console.error('Claim failed:', claim.detail) // never throws — read detail
} else {
// serve claim.verificationHash at claim.verificationUrl, then call verifyDomain
}

The standalone function is also exported if you don’t have a client:

import { claim402IndexDomain } from '@piprail/sdk'
const claim = await claim402IndexDomain('api.example.com', { contactEmail: 'you@example.com' })
// → { ok: true, domain: 'api.example.com', verificationHash: '…', verificationUrl: '…', … }
interface DomainClaim {
ok: boolean
domain: string // the host that was claimed
verificationHash?: string // serve THIS as the entire body of verificationUrl
verificationToken?: string // the raw token (preimage of the hash)
verificationUrl?: string // where to serve it: /.well-known/402index-verify.txt
instructions?: string // 402 Index's own human instructions
httpStatus?: number
detail?: string // failure reason when ok:false
}

Serve verificationHash as the entire body of the response at verificationUrl — it is the SHA-256 of the issued token, and 402 Index fetches that one file to confirm you control the host.

Once the file is live, call client.verifyDomain(urlOrDomain) (or the standalone verify402IndexDomain). 402 Index re-fetches the proof and, on a match, approves the domain.

const res = await client.verifyDomain('api.example.com')
// → { ok: true, status: 'verified', servicesCount: 3 }
interface DomainVerification {
ok: boolean
domain: string
status?: string // 402 Index's status string, e.g. 'verified'
servicesCount?: number // how many pending listings the verification approved
httpStatus?: number
detail?: string // failure reason when ok:false
}

servicesCount is the payoff: it tells you how many of your pending listings on that domain just went searchable. A subsequent discover() will find them.

Once a domain is verified, you don’t have to re-verify per listing. A register() from a verified domain comes back live rather than pending-review — the RegisterOutcome reports visibility: 'live' directly, with no review delay.

If you’re working below the client — a build script, a server-side tool — both steps are plain exported functions:

FunctionReturnsNotes
claim402IndexDomain(domainOrUrl, opts?)DomainClaimopts.contactEmail is optional 402 Index metadata.
verify402IndexDomain(domainOrUrl)DomainVerificationCall after the proof file is live.

The client methods client.claimDomain() / client.verifyDomain() are thin wrappers over these — use whichever fits.

Index payloads name chains by slug (base) or CAIP-2 (eip155:8453). normalizeNetwork(network) folds a recognised slug to its CAIP-2 form and passes a value that’s already CAIP-2 through unchanged. An unknown slug is returned as-is — treat that as “unresolved”, not a confident mismatch.

import { normalizeNetwork } from '@piprail/sdk'
normalizeNetwork('base') // → 'eip155:8453'
normalizeNetwork('eip155:8453') // → 'eip155:8453' (unchanged)
normalizeNetwork('madeupchain') // → 'madeupchain' (unresolved, not dropped)

The low-level register functions return a bare RegisterOutcome — no visibility, no note. decorateOutcome(outcome) projects the static directory lifecycle facts onto it, so an agent gets visibility + note without a second lookup. It’s idempotent, and respects a visibility the adapter already set (e.g. a verified-domain register that came back 'live').

import { register402Index, decorateOutcome } from '@piprail/sdk'
const outcome = decorateOutcome(await register402Index({ url: 'https://api.example.com/report' }))
// outcome.visibility → 'pending-review' (or 'live' from a verified domain)
// outcome.note → one-line caveat for this index