Read your first
document in 60s.
One POST turns an identity card, a licence or an invoice into validated JSON. No SDK to install, no request to sign, no queue to poll.
Quickstart
Create an extractor in the app and describe the fields you want. Copy its key from the extractor page. Post a document to it. That is the whole setup.
curl -X POST "https://vision.olee.cc/api/v1/extract" \ -H "Authorization: Bearer YOUR_KEY" \ -F "image=@nic.jpg"
Authentication
Send the extractor key as a bearer token. Nothing to sign, no handshake. We keep only a SHA-256 hash of the key, so it cannot be read back out of our database, and a leaked key is fixed by regenerating it on the extractor page. The old one dies the moment you do.
The key used to go in the URL path. That form is removed and now answers 410 with code key_in_url_removed. A URL is written down by every access log, proxy and browser history it passes through, and a header is not, so a credential in a path is a credential already copied somewhere you do not control. Your key has not changed and does not need regenerating, it moves, and the change is one line.
# REMOVED. Returns 410, code "key_in_url_removed". curl -X POST "https://vision.olee.cc/api/v1/extract/YOUR_KEY" -F "image=@nic.jpg" # Your key has not changed and does not need regenerating. # It moves into a header: curl -X POST "https://vision.olee.cc/api/v1/extract" \ -H "Authorization: Bearer YOUR_KEY" \ -F "image=@nic.jpg"
Lock the key to your server. Each extractor can hold a list of IP addresses, on its page in the app. Leave it empty and any address with a valid key works. Put your server's address in it and every other address is refused with 403 before a read is spent, which is what makes a copied key worthless. Only do this if the calling server has a fixed address: most serverless hosts do not, and will lock themselves out.
Either way, one alert runs on every extractor and needs no setup. A day whose read count is far above its normal emails the owner. It blocks nothing. It exists so a key that leaked is noticed in a day rather than at the end of the month.
Call it from your server. A key in browser code is a key anyone can read and spend, in a header or in a URL. This endpoint answers no cross-origin browser call and there is no CORS switch, because there was no version of that which was safe. To scan from a browser, use the capture widget: your server trades the key for a token worth one scan, on one site, for fifteen minutes.
The request
Send the bytes as multipart, or send a URL we fetch. Both take the same key.
curl -X POST "https://vision.olee.cc/api/v1/extract" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "image_url": "https://example.lk/nic.jpg" }'Consent goes in as a JSON object, or as flat form fields when you are already sending multipart.
curl -X POST "https://vision.olee.cc/api/v1/extract" \ -F "image=@nic.jpg" \ -H "Authorization: Bearer YOUR_KEY" \ -F "consent_obtained=true" \ -F "consent_reference=ticket-4471" \ -F "consent_method=signed_form"
The response
The keys are the ones your extractor defines, in snake_case, and a field we could not find comes back null rather than a guess. A character nobody could read is a missing value, not an invented one. If your prompt asks for confidence per field, or for a name in three scripts, you get that shape instead. The envelope stays the same.
{
"success": true,
"data": {
"nic_number": "200000112345",
"full_name": "DILSHAN ABEYGUNAWARDANA",
"date_of_birth": "2000-01-01",
"issue_date": null
}
}A refusal carries a machine-readable code alongside the sentence.
{
"error": "Read allowance exhausted. Buy extra reads or upgrade your plan.",
"code": "quota_exhausted",
"included": 1500,
"used": 1500
}Status codes
Webhooks
Turn one on for an extractor and every finished read is POSTed to your URL as well as returned in the response. The body carries the extractor id, the request id, the status, the same data and a timestamp.
Limits and metering
Grab a key and send your first document.