instantscan.io
Document scanning you can embed in an afternoon
Your users photograph a document; you get a cropped, deskewed PDF. One script tag on your page, the heavy computer vision on ours, and a signed webhook when it is done.
The problem
A file input is not a scanner
Ask someone to "upload a photo of your ID" and you get a tilted, shadowed, 8 MB JPEG of a document lying on a duvet. Then someone on your team crops it by hand, or your verification vendor rejects it and the user drops out.
Before
One flat photo. No edge detection, no deskew, no page ordering. Whatever the camera app produced is what your back office has to work with.
The usual fix
Build it yourself: getUserMedia, OpenCV, contour finding, a homography, PDF assembly, then months of device-specific camera bugs. It is a product, not a ticket.
After
A guided capture UI that finds the page edges live, corrects the perspective, stacks multiple pages and hands you one clean PDF. Two lines of integration.
How it works
Session in, PDF out
Every integration is the same three steps, whether the scanner opens in a modal on your page or on a phone that scanned a QR code.
Open a session
Your server (or the SDK, with a publishable key) calls POST /v1/sessions.
You get a session id, a short-lived signed token and a scan_url.
The user scans
The hosted scanner opens on our origin — in an iframe, or on their phone. Detection runs on their device; the document never passes through a third party.
You get the PDF
A signed scan.completed webhook hits your endpoint, and the SDK hands the same
file to your form. Both come from one event.
Integration
Pick the front door that fits your stack
The declarative path changes nothing about how your form submits: the scanned PDF is
injected into your existing <input type="file"> as a real
File, with a change event, so your validation and upload code stays
exactly as it is.
The keys in these samples are the seeded test keys from a local install. They work against a local API and debit the demo tenant.
<!-- 1. the launcher -->
<script src="https://instantscan.io/v1/scanner.js"
data-key="pk_test_demo"
data-api="https://api.instantscan.io"></script>
<!-- 2. the file input you already have -->
<input type="file" name="proof_of_address" data-scanner />
import { Scanner } from "@scanner-cloud/embed-sdk";
const scanner = Scanner.init({
publicKey: "pk_test_demo",
apiBase: "https://api.instantscan.io",
});
// Resolves with a real File — drop it straight into your form.
const { file, pageCount } = await scanner.open({
metadata: { application_id: "app_1042" },
});
# Create the session on your server with a secret key.
curl -X POST https://api.instantscan.io/v1/sessions \
-H "Authorization: Bearer sk_test_demo" \
-H "Content-Type: application/json" \
-d '{"metadata": {"application_id": "app_1042"}}'
# => { "id": "ss_...", "scan_url": "https://scan.instantscan.io/?t=...",
# "client_token": "...", "expires_at": "..." }
#
# Render scan_url as a QR code, or open it in an iframe.
# The result arrives as a signed scan.completed webhook.
What you get
Everything the scan needs after the shutter
Capture is the interesting part, but the reason integrations stall is everything around it: keys, origins, delivery, retries, branding, and knowing whether it worked.
Detection that holds up on a kitchen table
Live edge detection, perspective correction and adaptive contrast, running on the device's own camera stream. Not a photo upload with a crop tool.
6 KB on your page
The launcher is a thin iframe opener. The 9.6 MB computer-vision engine stays on our origin, so your bundle does not grow and you never redeploy to get a fix.
Three front doors, one session
Drop-in file input, programmatic modal, or a QR code for desktop users with no camera. Same session object, same result, same webhook.
Delivery you can debug
Signed webhooks with a full delivery log: request body, response status, duration and every retry. Replay any delivery from the console.
Looks like your product
Six languages, your accent colour, corner radius, brand name and success screen. Validated server-side, so a bad value is a 400 and never a broken camera UI.
Prepaid credits, no contract
One credit per scanned document, however many pages — opening a session is free. The 250 signup credits are the sandbox. The ledger is append-only, so the balance always reconciles.
Security
The browser is public. We designed for that.
Anything we ship to a browser can be read by anyone — the launcher, the publishable key, the session token. So none of them are secrets. Enforcement is server-side, and the things a leaked value can do are deliberately tiny.
A leaked publishable key opens sessions. That is all.
It cannot read a result, list a session or change a setting — and only works from an origin you allowlisted.
Client tokens are single-use and expire in minutes.
Signed with {sid,tid,iat,exp,jti}. Reusing one to upload a second
document returns 409.
Result URLs are signed, not guessable.
The signature is the authorisation, and it expires. Tamper with the id and you get a 403.
Get started
Scan your first document in ten minutes
Sign up, copy your test key, paste one script tag. The 250 signup credits cover building and demoing the whole flow before you buy a pack.