API reference

Embed the input in the browser; verify proofs on your server. Machine-readable: openapi.json.

1. The SDK

<script src="https://www.proofoftype.com/sdk/v1.js" async></script>

It mounts every [data-proofoftype] element, and exposes window.ProofOfType. Every event is also dispatched on the element as a DOM event, e.g. el.addEventListener('proofoftype:verified', e => e.detail).

Attributes / options

AttributeOptionDefaultWhat
data-namenameproofoftypeForm field name. The proof goes in <name>_proof.
data-placeholderplaceholder"Type here…"Placeholder text.
data-max-wordsmaxWords500Word limit, 10–3000.
data-themethemeautoauto, light or dark.
data-buttonbuttontrue (false in a form)Show the built-in Verify button. In a form, the form's submit button verifies.
data-button-labelbuttonLabelVerify I typed thisLabel of the built-in button.
data-formformtrueHandle the surrounding <form> (fill hidden fields, verify on submit).

Methods

MethodReturnsWhat
ProofOfType.mount(el, options)InputPut an input in an element (or a selector).
ProofOfType.scan(root?)—Mount any new [data-proofoftype] elements (after you add them).
input.verify()Promise<Result>Ask the person to confirm on their phone; resolves with the text and proof.
input.reset()—Clear the text and the proof.
input.focus()—Focus the input.
input.on(event, fn) / offInputListen to events.
input.destroy()—Remove the input.
input.resultResult | nullThe last verified result.

Events

EventDataWhen
ready{ signedIn, username }The input loaded.
signin{ username }The person signed in with Proof of Type.
change{ words, chars }While typing (counts only; the text comes with the proof).
verifiedResultA proof is ready.
error{ code, message }Not verified: not_signed_in, empty, too_long, needs_app, cancelled, session, human_score, rate_limit, …
resize{ height }The input changed height (the SDK resizes the frame for you).

Result

{
  text:    "The exact text, as typed",      // plain text; this is what's hashed
  html:    "<p>The exact text, as typed</p>", // with formatting (bold, lists…)
  token:   "eyJhbGciOiJFZERTQSIs…",        // the proof (JWT)
  author:  "yogev",                           // Proof of Type username
  score:   87,  wpm: 52,  words: 6,  chars: 22,
  proofId: "pot_…",
  url:     "https://www.proofoftype.com/v/eyJhbGci…"             // public proof page
}

2. Verify a proof: POST /api/v1/verify

Call it from your server before you save the text. CORS-enabled; no API key needed.

FieldTypeWhat
tokenstring, requiredThe proof token.
textstring, optionalThe text you received. Returns text_matches.
originstring, optionalYour site, e.g. https://example.com. Returns origin_matches.
Request
curl -s https://www.proofoftype.com/api/v1/verify \
  -H 'Content-Type: application/json' \
  -d '{"token":"eyJhbGciOiJFZERTQSIs…","text":"Hello, world","origin":"https://your-site.com"}'
Response
{
  "valid": true,
  "text_matches": true,
  "origin_matches": true,
  "proof": {
    "id": "pot_3f9…", "origin": "https://example.com", "author": "yogev",
    "typed_at": "2026-10-11T18:20:31.000Z", "signed_on": "ios",
    "score": 87, "wpm": 52, "words": 6, "chars": 22,
    "text_sha256": "a59…", "version": 1
  }
}

Invalid tokens return { "valid": false, "reason": "bad_signature" | "malformed" | "unknown_key" | "bad_claims" }.

Accept a proof only if valid, text_matches and origin_matches are all true. Keep the proof id: one proof backs one piece of text.

3. Verify offline: the proof token

Tokens are JWTs signed with EdDSA (Ed25519). Public keys: https://www.proofoftype.com/.well-known/jwks.json. Issuer https://www.proofoftype.com, audience = your origin.

ClaimWhat
issAlways https://www.proofoftype.com
subThe proof id (pot_…)
audThe site the text was typed on (its origin, read by the browser)
iatWhen it was verified (Unix seconds)
typed_byAlways "human"
authorThe Proof of Type username who typed and signed it
signed_on"ios" or "android": the phone that signed it
text_sha256SHA-256 (hex) of the exact UTF-8 text
scoreHuman score, 0–100 (typing rhythm and touch)
wpmTyping speed, words per minute
wordsWord count
charsVisible characters (no spaces)
vToken format version (1)
Node.js (jose)
// Or verify offline with our public key (EdDSA / Ed25519 JWT).
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { createHash } from 'node:crypto';

const JWKS = createRemoteJWKSet(new URL('https://www.proofoftype.com/.well-known/jwks.json'));

const { payload } = await jwtVerify(token, JWKS, {
  issuer: 'https://www.proofoftype.com',
  audience: 'https://your-site.com',
});
const hash = createHash('sha256').update(text, 'utf8').digest('hex');
if (payload.text_sha256 !== hash) throw new Error('Not the signed text');

4. Public proof pages

https://www.proofoftype.com/v/<token> shows who typed it, where and when, and lets anyone paste the text to check it's an exact match. Link it next to the text if you like.

Limits

  • Up to 3,000 words per input; 60 verifications per account per hour.
  • One typing session backs one proof; typing must take real time (it's timed on our server).
  • Your page must send a referrer origin to the frame (the SDK sets referrerpolicy="origin").

Changelog