HTTP reference for the public Verifier-as-a-Service. For the underlying byte format, see the protocol spec.
Submit a PVAIO bundle (HR-JSON 4 or naked envelope) and receive a cryptographic verdict. Accepts anonymous traffic; a Bearer pvaio_live_… header unlocks the higher daily quota.
POST /api/pvaio/verify
Content-Type: application/json
Authorization: Bearer pvaio_live_<32-hex> # optional
{
"bundle": {
"@version": "hr-json-4+pvaio-1",
"documentTitle": "…",
"sections": [...],
"optimizedText": "…",
"changesApplied": [...],
"originalText": "…", // OPTIONAL: enables anchor replay
"pvaio": {
"ledger": { /* LedgerEnvelope — see /protocol §3 */ },
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n…",
"keysetUrl": "https://issuer.example/.well-known/pvaio-keys.json",
"projectedScore": 85,
"projectedAtsScore": 85,
"projectedSemanticScore": 85
}
}
}
// A naked envelope is also accepted:
{ "envelope": { /* LedgerEnvelope */ }, "originalText": "…" }200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1714228123
X-PVAIO-Verifier: a1b2c3d4e5f67890 # this deployment's fingerprint
{
"ok": true,
"verdict": "valid", // "valid" | "invalid" | "expired" | "key_unknown"
"checks": {
"merkle": true,
"hmac": true,
"ed25519": true,
"replay": null, // null if originalText not provided
"revocation": "not_revoked", // v2.6+: "not_revoked" | "revoked" | "unknown"
"replay_nonce": "absent", // v2.6+: "fresh" | "seen_same_root" | "conflict" | "absent"
"fabricated_free": "ok" // v2.7+: "ok" | "fail" | "absent"
},
"merkleRoot": "7c…",
"engineVersion": "2.7.0",
"issuedAt": "2026-04-27T08:12:03.141Z",
"signedBy": "a1b2c3d4e5f67890", // first 16 hex of the verifying pubkey fingerprint
"stats": { "exact": 14, "fuzzy": 3, "semantic": 1, "fabricated": 0 }
}curl -sS https://octodrives.com/api/pvaio/verify \
-H 'Content-Type: application/json' \
--data-binary @alice-resume.hrjson.json | jq
# or submit a naked envelope:
curl -sS https://octodrives.com/api/pvaio/verify \
-H 'Content-Type: application/json' \
-d '{"envelope": '"$(jq '.pvaio.ledger' alice.hrjson.json)"'}'Public. CDN-cacheable (max-age=300). Returns the deployment's active Ed25519 signing keys. Fetch once, pin the fingerprint, verify forever.
GET /.well-known/pvaio-keys.json
200 OK
{
"issuer": "pvaio",
"engineVersion": "2.7.0",
"keys": [
{
"alg": "ed25519",
"pem": "-----BEGIN PUBLIC KEY-----\n…",
"fingerprint": "a1b2c3d4e5f67890",
"status": "active"
}
]
}JSON Schema draft-07 for the HR-JSON 4 + PVAIO envelope. Drop into your ATS's validator to reject malformed bundles before they hit the verifier.
Public SVG badge for a specific merkle root. Returns the three-state badge (verified / unverified / tampered). Safe to embed in READMEs, careers pages, or any <img>-capable surface.
GET /api/badge/7c3…
200 OK
Content-Type: image/svg+xml
Cache-Control: public, max-age=60, s-maxage=600Tier Requests Bucket Header feedback
─────────────────────────────────────────────────────────────────────
Anonymous 120 / 60 s per IP X-RateLimit-*, Retry-After
Bearer pvaio_live_… token daily_quota per token X-RateLimit-*, Retry-After
+ 1000 / day per token (429 daily_quota_exceeded)
Exceeded → 429 with Retry-After (seconds). The sliding window resets
relative to your oldest in-window request, not a fixed wall clock.| status | code | meaning |
|---|---|---|
| 400 | invalid_json | Body was not JSON. |
| 200 | missing_ledger | No pvaio envelope found in the bundle. |
| 200 | malformed_ledger | Envelope missing required fields. |
| 200 | merkle_root_mismatch | anchors[] rehash does not match ledger.merkleRoot. |
| 200 | hmac_signature_mismatch | HMAC failed against the issuer's secret. |
| 200 | ed25519_signature_mismatch | Ed25519 signature failed under the trusted key. |
| 200 | anchor_replay_mismatch | originalText was provided and one or more anchor leaves failed to re-derive. |
| 200 | unrecognised signing key | verdict='key_unknown' — no fingerprint-matching key available. |
| 401 | invalid_token | Authorization: Bearer header failed the pvaio_live_<hex> format check. |
| 429 | rate_limited | Over 120 req / 60 s for this IP. Check Retry-After header. |
| 429 | daily_quota_exceeded | Authenticated token hit its per-day quota. |
| 405 | method_not_allowed | GET on /api/pvaio/verify — POST only. |
| 500 | verifier_error | Internal error. Retry; report if persistent. |
Note: a failed verification is still HTTP 200. The failure lives in the JSON body (ok: false, verdict: "invalid"). Reserve 4xx/5xx for transport errors, not verdicts.