{"name":"FabTally URL Attestation","version":"0.1.0","attestor":"attest.fabtally.com/v0","disclaimer":"point-in-time observation, not a legal attestation","endpoint":{"method":"POST","url":"https://fabtally.com/agent/v1/attest","price":"$0.01","priceUsdc":"10000"},"payment":{"protocol":"x402","x402Version":2,"scheme":"exact","network":"eip155:8453","chainId":"eip155:8453","asset":"USDC","payTo":"0xccB5d25C698FdfdaA7B21d54088774cF512A90e3","facilitator":"https://facilitator.payai.network"},"description":"Independent point-in-time observation of a public URL, signed with ed25519. Fetches the URL from a neutral third-party host and returns HTTP status, final URL after redirects, sha256 of the exact bytes received, size, and cache headers — plus your chosen check (exists / contains a string / content hash). Useful when an agent needs a durable, verifiable record that a page said something at a specific moment.","inputSchema":{"bodyType":"json","type":"object","required":["url","check"],"properties":{"url":{"type":"string","description":"Absolute http(s) URL to observe. Must resolve to a public IP address; private, loopback, link-local and other reserved ranges are refused at every redirect hop."},"check":{"type":"string","enum":["exists","contains","hash"],"description":"What to assert. \"exists\" → boolean, true when the fetch ended in a 2xx. \"contains\" → boolean, true when the response body contains `needle` (case-sensitive, UTF-8). \"hash\" → the sha256 hex of the body, or null when the fetch was not 2xx."},"needle":{"type":"string","description":"Substring to look for. Required when check=\"contains\", ignored otherwise. 1024 chars max."}},"example":{"url":"https://example.com/pricing","check":"contains","needle":"$49/mo"}},"outputSchema":{"type":"object","properties":{"url":{"type":"string","description":"The URL as requested (normalized)."},"fetchedAt":{"type":"string","format":"date-time","description":"UTC ISO-8601 timestamp of the observation."},"check":{"type":"string","enum":["exists","contains","hash"]},"needle":{"type":["string","null"]},"httpStatus":{"type":["integer","null"],"description":"Status of the final hop; null if the fetch failed."},"finalUrl":{"type":["string","null"],"description":"URL after following redirects."},"redirects":{"type":"array","description":"Every redirect hop taken, in order.","items":{"type":"object","properties":{"from":{"type":"string"},"status":{"type":"integer"},"to":{"type":"string"}}}},"result":{"type":["boolean","string","null"],"description":"Boolean for exists/contains; sha256 hex string for hash; null if unobtainable."},"sha256":{"type":["string","null"],"description":"sha256 (hex) of the exact response bytes received."},"bodyBytes":{"type":["integer","null"]},"headersSubset":{"type":"object","properties":{"content-type":{"type":["string","null"]},"last-modified":{"type":["string","null"]},"etag":{"type":["string","null"]}}},"error":{"type":["object","null"],"description":"Set when the observation could not be completed as a 2xx fetch (DNS failure, timeout, blocked address, non-2xx status, oversized body). The attestation is still returned and still signed — 'the URL did not respond at time T' is itself an observation.","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"attestor":{"type":"string","enum":["attest.fabtally.com/v0"]},"disclaimer":{"type":"string","enum":["point-in-time observation, not a legal attestation"]},"signature":{"type":"object","description":"ed25519 signature over the canonical JSON of this object with the `signature` field removed and keys serialized in the documented order. Verify with the public key from GET /agent/v1/attest/schema.","properties":{"alg":{"type":"string"},"keyId":{"type":"string"},"publicKeySpkiB64":{"type":"string"},"value":{"type":"string","description":"base64 signature"},"canonicalization":{"type":"string"}}}},"example":{"url":"https://example.com/pricing","fetchedAt":"2026-07-30T19:05:00.000Z","check":"contains","needle":"$49/mo","httpStatus":200,"finalUrl":"https://example.com/pricing","redirects":[],"result":true,"sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","bodyBytes":18422,"headersSubset":{"content-type":"text/html; charset=utf-8","last-modified":null,"etag":"W/\"47f0-x\""},"error":null,"attestor":"attest.fabtally.com/v0","disclaimer":"point-in-time observation, not a legal attestation","signature":{"alg":"ed25519","keyId":"…","publicKeySpkiB64":"…","value":"…","canonicalization":"…"}}},"limits":{"timeoutMs":10000,"maxBodyBytes":2097152,"maxRedirects":3,"schemes":["http","https"],"method":"GET","acceptEncoding":"identity (the sha256 is over the exact bytes on the wire, undecoded)","blocked":"URLs resolving to private, loopback, link-local, CGNAT, multicast or otherwise reserved addresses are refused, at the first hop and at every redirect."},"verification":{"publicKey":{"alg":"ed25519","keyId":"38adc17dc451a423","publicKeySpkiB64":"MCowBQYDK2VwAyEAabeuiwmvccrN7KWPYHEEx1ubBEDUnDEbLmgwGXluDy8=","publicKeyRawHex":"69b7ae8b09af71cacdeca58f607104c75b9b0440d49c311b2e683019796e0f2f"},"signedFieldOrder":["url","fetchedAt","check","needle","httpStatus","finalUrl","redirects","result","sha256","bodyBytes","headersSubset","error","attestor","disclaimer"],"howTo":"Remove the `signature` key from the attestation object, re-serialize the remaining fields as JSON in signedFieldOrder (no whitespace, JS JSON.stringify semantics), then verify signature.value (base64) over those UTF-8 bytes with the ed25519 public key.","nodeExample":"crypto.verify(null, Buffer.from(canonicalJson), crypto.createPublicKey({key: Buffer.from(publicKeySpkiB64,'base64'), format:'der', type:'spki'}), Buffer.from(sig.value,'base64'))"},"honesty":{"whatTheSignatureProves":"that this service produced this record, unmodified since.","whatItDoesNotProve":"that the observation is correct, that the remote server did not serve us something different from what it served you, or anything with legal force. We are a witness, not a notary."},"errorCodes":{"bad_request":"input failed validation (HTTP 400, no payment is settled)","bad_url":"url is not a valid absolute URL, or carries credentials (HTTP 400)","bad_scheme":"non-http(s) scheme (HTTP 400)","blocked_address":"host resolves to a non-public address (returned inside a signed attestation)","dns_failed":"DNS lookup failed","timeout":"no response within 10000 ms","body_too_large":"response exceeded 2097152 bytes","too_many_redirects":"more than 3 redirects","non_2xx":"the fetch completed but the final status was not 2xx","fetch_failed":"connection/TLS failure"}}