Developers and integrations

Embed the signing workflow directly into your app.

<signitri-viewer> and <signitri-prepare> are standard web components with zero external dependencies. No build step and no framework. They run natively inside your own frontend rather than in an iframe beside it.

Explore the component codeRequest API access
Encapsulated Shadow DOM styling Themeable through standard CSS custom properties Framework agnostic, with zero third-party dependencies
The embeddable viewer

Load the module and place the HTML tag.

Importing the module defines the custom elements. After that they behave like native HTML tags: give one a document source, tell it who is signing, and handle the completion event.

There is no toolchain in this path at all. The files are plain ES modules that ship with their own isolated styles and typography, so the element looks native on a page that knows nothing about Signitri, without extra CSS overrides or a build step.

index.html
<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>
Viewer.jsx
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} />;
}
Framework compatibility

Native integration with React, Vue, Svelte, or plain JavaScript.

One thing is worth knowing before you write the wrapper. The elements accept standard JavaScript objects for page sources, recipient lists and terms definitions, but JSX props on an unknown tag become HTML attributes, which are strings. So in React the objects are set as properties, while the methods setTerms() andsignAs() are called through a ref.

The order is not arbitrary either. open() returns a promise 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 and Svelte bind properties directly to custom element props, so their versions need less code again.

Extensibility

Flexible page rendering and isolated styling.

Rendering engine

The viewer is not wired to one engine. It consumes an interface, and Signitri’s fast WebAssembly 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.

PageSource
interface PageSource {
  pageCount: number
  size(i): { w, h }                  // points
  render(i, scale): Promise<ImageBitmap|HTMLCanvasElement>
  text?(i): Promise<TextLayer>
}

Style encapsulation

The element’s styles are fully enclosed in a shadow root. Your global stylesheets will not break the viewer, and its own styles will not leak into your layout. The custom properties on the host are the contract, and they are the only thing that crosses. The element follows the operating system’s color scheme, and adata-theme attribute forces either one.

your-styles.css
/* 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);
}
API reference

Custom element methods and event reference.

<signitri-viewer>

open(source)
Attaches a page source and renders the document pages asynchronously. Everything else waits on it.
signAs(recipientId, recipients)
Activates the fields belonging to one signer. Everyone else’s stay visible but inert, so the signer can see the whole document is accounted for.
setTerms(terms)
Requires the signer to accept your terms before any field can be filled. Four shapes, because senders have all four: none, text, link, document.
download(name)
Triggers a browser download of 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()
Returns a structured object of field names, kinds and entered values, ready for the writer.
toJSON()
Annotations as plain data.
signatureStore
Binds your own storage handler for saving and reusing signatures across sessions. 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
Timestamped proof of acceptance: who accepted, a digest of the text they saw, and when.

<signitri-prepare>

open(source)
Loads the same page source the viewer takes, for field placement and recipient setup.
enableAI(client)
Scans the document’s own text layer and suggests where the fields belong.
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.

Platform API

A REST API, live today.

Six endpoints and one credential. You can read documents, their recipients and their field layout, and you can create a document from a template and send it for signature. Everything below is deployed and callable now, and nothing is documented here that is not.

Authentication

A workspace owner issues a key under API in the app. It is shown once, because only a SHA-256 of it is stored and nobody at Signitri can look it up afterwards.

curl https://app.signitri.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_a3f19c2e.…"

Server to server only. No CORS headers are sent, deliberately: a key must never reach a browser page.

Scopes

  • documents:read — list and read documents and templates
  • documents:write — create a document from a template
  • documents:send — create it and send it for signature

A key reaches documents and nothing else. It cannot add a colleague, change a plan or read anybody's identity check; those stay behind a signed-in session.

Endpoints

MethodPathReturns
GET/v1/documentsEvery document in the workspace. ?status= and ?limit= (max 100).
GET/v1/documents/{id}One document, with its recipients and their signing state.
GET/v1/documents/{id}/analysisPage geometry, the coordinate contract, and the current field layout.
GET/v1/templatesEvery template in the workspace, with its roles and variables.
GET/v1/templates/{id}One template. Read this to learn the role ids you must fill.
POST/v1/documentsCreate a document from a template and send it. See below.

Coordinates

Returned by /analysis in the payload rather than only in prose, because a rectangle authored against the wrong origin lands on a clause and nothing on your side can tell that it did.

{ "unit": "pt", "origin": "top-left", "yAxis": "down" }

Errors

One shape everywhere. Branch on code; message is for whoever reads the log.

{ "error": {
    "code": "insufficient_scope",
    "message": "This key does not have documents:send."
} }

400 invalid_request · 401 unauthenticated, invalid_key · 402 over_limit · 403 insufficient_scope, forbidden, orphaned_key · 404 not_found · 405 method_not_allowed · 429 rate_limited, 120 requests a minute per key · 500 internal. A 402 or 403 on POST /v1/documents still returns the document id: the draft was created and is waiting.

Sending from a template

A template is a document somebody prepared once, in the editor, with the pages in front of them — and then took the people out of. What is left is a list of roles and a list of variables. You supply both, and it sends.

This is the only way to create a document, and that is deliberate. Placing a signature field means knowing where the signature line is on a page you cannot see. A template is the artefact where that was already decided correctly, so the API never has to guess.

Roles sign in the order the template defines. That order is what puts each signature on the right line, so it is fixed by the template and not by the order of your JSON.

curl -X POST https://app.signitri.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_a3f19c2e.…" \
  -H "Content-Type: application/json" \
  -d '{
    "template": "8f2c…",
    "name": "Lease for {{ property }}",
    "variables": { "property": "14 Rosebank Road" },
    "assignments": {
      "r_landlord": { "name": "Ayanda Mokoena",
                      "email": "ayanda@example.com" },
      "r_tenant":   { "name": "Marike du Toit",
                      "email": "marike@example.com" }
    }
  }'

send: false creates the draft and stops, for a human to review and send. Variables you leave out stay visible as {{ like_this }} rather than becoming an empty gap nobody notices.

What a refused send does

A send can be refused for reasons you can fix: the monthly allowance, a spend cap on additional documents, an unpaid card, a permission the key's owner no longer has. When that happens the document is still created and waiting in the workspace, and the response says so with 402 or 403 and the document id. Nothing you built is thrown away because the meter ran out. Webhooks are not built yet.

Tell us what you need
Where this stands

What you can build against today.

Working now

  • Both elements, loaded as ES modules from files you serve yourself.
  • Signature capture, whether 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.

Ready to embed verified signatures in your application?

The elements are the same ones Signitri runs on. While the package is unpublished, you get the component files and the technical specs by arrangement. Contact our engineering team, and say what you are building.

Request the element filesRead the security overview