One typed boundary for every signature.
A1/PKCS#12, PDF/PAdES, XML-DSig, and remote providers, all Effect-native. Swap the signer, keep the program — browser or server, typed errors, Redacted secrets.
- Clicksign
- Assinafy
- ZapSign
- DocuSeal
- Documenso
Live · in your browser
Sign a real PDF, right here.
No backend, no upload, no SaaS. Drop in a PDF, click where the signature goes, load your A1 (e-CPF / e-CNPJ), and sign with WebCrypto. The document and the private key never leave the page.
Upload your PDF
Rendered locally with pdf.js.
Click to place
Drop the PAdES signature rectangle anywhere on any page.
Sign with your A1
Your .pfx and password sign in-browser via WebCrypto.
100% client-side · WebCrypto · PAdES (AdES-BES)
Auto-signature
Position once. Sign the whole stack.
Several real contracts are generated in your browser with @react-pdf/renderer, rendered on the same pdf.js placement canvas the signer uses, then auto-placed and signed in a queue — one document at a time.
Remote providers
One SDK for every signer.
Eight signature providers, one request shape. Pick a provider — the create*SignatureRequest call swaps, your application code doesn't.
- Adapters
- Every provider is one create*SignatureRequest over a shared SignatureHttpClient — provider-specific code stays out of your app.
- Typed errors
- Nothing throws. Every failure is one SignatureKitError on the Effect channel, tagged with the provider and a literal code.
- Redacted secrets
- Tokens and API keys stay wrapped in Redacted until the HTTP boundary — never logged, never leaked.
- One request shape
- The same title / documents / recipients request normalizes to a RemoteSignatureRequest across all eight providers.
import { createClicksignSignatureRequest } from "@signature-kit/clicksign"
import { signatureHttpClientLive } from "@signature-kit/core/http"
import { Effect, Redacted } from "effect"
const request = yield* createClicksignSignatureRequest(
{
accessToken: Redacted.make(process.env.CLICKSIGN_TOKEN ?? ""),
environment: "sandbox",
locale: "pt-BR",
autoClose: true,
},
{
title: "Membership agreement",
documents: [{ fileName: "agreement.pdf", mimeType: "application/pdf", content: pdfBytes }],
recipients: [{ name: "Bruno Lima", email: "bruno@example.com", role: "signer" }],
},
).pipe(Effect.provide(signatureHttpClientLive))Seven capabilities
Everything you sign, one API.
A1 / PKCS#12 in the browser or on the server, PDF·PAdES and XML-DSig, eight remote providers, one typed error channel, and a React surface for in-browser signing — the same Effect-native ergonomics across every backend.
import { signatures } from "@signature-kit/core/signatures"a1SignaturesLayer({ pfx, password: Redacted.make(password) })signPdf({ pdf, policy: "pades-icp-brasil" })signXml({ xml, referenceId: "nfe-1" })createClicksignSignatureRequest(config, request)Effect.catchTag("SignatureKitError", handle)import { signBrowserPdf } from "@signature-kit/pdf/browser"Two steps
Your first signature, end to end.
Install the core package and the A1 signer, then construct one layer and run a program that inspects the certificate and signs.
bun add @signature-kit/core @signature-kit/a1npm install @signature-kit/core @signature-kit/a1pnpm add @signature-kit/core @signature-kit/a1yarn add @signature-kit/core @signature-kit/a1@signature-kit/core is the typed entry point. Each signer and format installs only when you reach for it: /a1/pdf/xml
import { a1SignaturesLayer } from "@signature-kit/a1/signer"
import { signatures } from "@signature-kit/core/signatures"
import { Effect, Redacted } from "effect"
// Load the A1 / PKCS#12 container once — pfx is the .pfx/.p12 *bytes*.
const layer = a1SignaturesLayer({
pfx,
password: Redacted.make(process.env.A1_PASSWORD ?? ""),
})
// Inspect the certificate, then sign — typed errors, no thrown exceptions.
const program = Effect.gen(function* () {
const identity = yield* signatures.inspect()
const artifact = yield* signatures.sign({
content,
algorithm: "rsa-sha256",
})
return { identity, artifact }
}).pipe(Effect.provide(layer))
const { identity, artifact } = await Effect.runPromise(program)runPromiseresolves to the parsedidentityand the signedartifact. From here, swap the A1 layer for a format —signPdforsignXml— or hand the program to a remote provider.
Ready when you are
Ship the signing layer once.
Open source, MIT licensed, Effect-native. A1, PDF, XML, and remote providers behind one typed program — every failure a value you can match on.