The document surface is one HTML tag.
<signitri-viewer> and <signitri-prepare> are custom elements built on web standards. No build step, no framework, no dependencies — they run inside your own application rather than beside it.
Load the module, write the tag.
Importing the module defines the element. After that it behaves like any other tag your framework does not recognize: give it a page source, tell it who is signing, and listen for what happened.
There is no toolchain in this path at all. The files are plain ES modules and they carry their own stylesheet and fonts, so the element looks right on a page that knows nothing about Signitri.
<script type="module" src="/signitri/viewer.js"></script>
<signitri-viewer id="lease"></signitri-viewer>
<script type="module">
import Signitri from '/signitri/signitri.js';
const doc = await Signitri.open('/lease.pdf');
const el = document.getElementById('lease');
// open() first: signAs paints fields that do not
// exist until the document has been read.
await el.open(doc.source());
el.signerName = 'Nkosi Dlamini';
el.setTerms({ mode: 'text', text: terms });
el.signAs('rcpt_2', recipients);
el.addEventListener('signitri-complete', () => {
send(el.values());
});
</script>export function Viewer({
source,
actor,
recipients,
terms,
signatureStore,
}) {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el || !source) return;
(async () => {
await el.open(source);
el.signatureStore = signatureStore ?? null;
el.setTerms(terms ?? { mode: 'none' });
el.signAs(actor ?? null, recipients ?? []);
})();
}, [source, actor, recipients, terms, signatureStore]);
return <signitri-viewer ref={ref} />;
}React, Vue, Rails, or a bare script tag.
One thing is worth knowing before you write the wrapper. The element takes objects — a page source, a recipient list, a terms definition — and JSX props on an unknown tag become HTML attributes, which are strings. So objects are set as properties, while the methods setTerms() and signAs() are called through a ref.
The order is not arbitrary either. open() is async and has to resolve before signAs(), because signAs paints fields that do not exist until the document has been read. Get it wrong and you see an empty document and no error.
That is the whole wrapper. Vue sets properties on custom elements by default, so its version is shorter still.
You supply the pages. You supply the palette.
Rendering comes from a page source
The viewer is not wired to one engine. It consumes an interface, and Signitri’s WASM engine returns one from doc.source(). A server-side renderer or a test double satisfies the same four members, and the element does not know the difference.
interface PageSource {
pageCount: number
size(i): { w, h } // points
render(i, scale): Promise<ImageBitmap|HTMLCanvasElement>
text?(i): Promise<TextLayer>
}Styling crosses in one direction only
The element’s styles live in a shadow root. Your CSS does not reach in and its CSS does not leak out. The custom properties on the host are the contract, and they are the only thing that crosses. It follows the operating system’s color scheme, and a data-theme attribute forces either one.
/* Your stylesheet. The element reads these off the host. */
signitri-viewer {
height: 80vh;
--sg-bg: var(--app-desk); /* the desk */
--sg-paper: var(--app-paper); /* the sheet itself */
--sg-accent: var(--app-accent);
--sg-line: var(--app-border);
}The whole surface, in one list.
<signitri-viewer>
- open(source)
- Attach a page source and render it. Async, and everything else waits on it.
- signAs(recipientId, recipients)
- Only that recipient’s fields respond. Everyone else’s stay visible and inert, so the signer can see the whole document is accounted for.
- setTerms(terms)
- Nothing can be filled until the signer accepts. Four shapes, because senders have all four: none, text, link, document.
- download(name)
- Hands the signer the document as it stands, at any point, not only at the end. The control hides itself when there is nothing to hand over.
- values()
- Field names, kinds and values, ready for the writer.
- toJSON()
- Annotations as plain data.
- signatureStore
- Any object with list, save, remove and rename. Leave it unset and saved signatures are off for that document.
- signerName · downloadName
- Prefills the typed-signature tab, and names the file the signer downloads.
Events it dispatches
- signitri-complete
- Every required field this signer owns is filled.
- signitri-field-signed
- One field now carries a signature. The detail is the field.
- signitri-terms-accepted
- Who accepted, a digest of the text they saw, and when.
<signitri-prepare>
- open(source)
- The same page source the viewer takes.
- enableAI(client)
- Field suggestions read from the document’s own text layer.
- toJSON()
- Recipients, fields, and where on the page each one sits.
- signitri-package
- The prepared document, ready to send for signature.
- signitri-ai-fields
- Suggested fields, for your own review step before they are placed.
- signitri-notice
- A message to show in your own chrome. The element never draws over your interface.
Custom events do not bubble into React’s synthetic system. Subscribe on the element.
The REST API is not published yet.
There is no endpoint reference on this page, because publishing one before the paths are final would cost you a rewrite. Here is the shape it will take, so you can judge whether it fits.
What it will cover
- Create a document from a PDF you upload.
- Place fields and assign each one to a recipient.
- Send for signature, and set the signing window.
- Read a document’s status and each signer’s verification result.
- Download the signed file and the evidence pack.
- Cancel a document that is still live.
Outbound webhooks
One delivery per state change, signed so you can tell it came from Signitri, retried until you acknowledge it.
- Document sent
- Signer opened the document
- Signer verified their identity
- Verification sent to manual review
- Signer signed
- Document completed
- Document cancelled or expired
Endpoint paths, payload shapes, authentication and rate limits are not final, and nothing above should be built against. Say what you need the API to do and you will get a straight answer on timing.
What you can build against today.
Working now
- Both elements, loaded as ES modules from files you serve yourself.
- Signature capture — drawn, typed, or reused from a saved set. Drawn signatures carry pressure and timing wherever the hardware reports it.
- Fields read from the PDF’s own AcroForm widgets, so a document prepared in Acrobat presents the same way here.
- Terms acceptance recorded alongside the signature, including a digest of the text the signer actually saw.
- Light and dark, through the --sg-* custom properties on the host.
Not yet
- A published package and a versioned URL to load the elements from. For now you serve the files.
- The REST API, API keys, and a sandbox environment.
- Outbound webhooks.
- Reference documentation beyond this page.
Put the signing surface in your own application.
The elements are the same ones Signitri runs on. While the package is unpublished, you get the files by arrangement — ask, and say what you are building.