Six processing tasks moved onto Start, Grow and Scale. OCR, Envelope OCR, Document Detection, Image Enhancement and Image Upscaling were each a separate plugin, and we have switched all six on across those three Filestack plans. Usage limits are unchanged.
This is the third of six posts on the Start, Grow and Scale processing tasks, one per task, covering what each one returns and where it belongs in an application.
Document Detection finds the sheet of paper inside a photograph and hands back the page on its own, straightened and cropped away from whatever was around it.
One path segment in front of the handle turns a 1400 x 1050 PNG photograph into a 1021 x 749 JPEG of the page.
Key takeaways
- The
doc_detectiontask returns an image rather than JSON, cropped to the detected page and converted to 8 bit greyscale. - We now include Document Detection on the Filestack Start, Grow and Scale plans at 1,000 images a month, alongside OCR, Envelope OCR, Image Enhancement and Image Upscaling.
preprocess:falsereturns the same crop in colour, so that parameter decides binarisation and lighting rather than the geometry.ocrreads a photographed invoice directly, so point text extraction at the uploaded handle and usedoc_detectionfor the page a person looks at.- Overage runs at $0.2 an image, the highest rate of the five, so chain
storebehind the task and keep the result as its own file.
How to call the document detection API
The task is a path segment in front of the handle:
https://cdn.filestackcontent.com/security=policy:POLICY,signature:SIGNATURE/doc_detection/HANDLE
With application security enabled, Intelligence tasks need a signed policy, and an unsigned request names the task that wanted one:
HTTP 403
security required for tasks: doc_detection
The policy is a base64 JSON object with an HMAC-SHA256 signature over it. This task needs the read and convert calls. The example below needs one dependency and a handle for an image already in your application:
pip install requests
import base64, hashlib, hmac, json, os, time
import requests
APP_SECRET = os.environ["FILESTACK_APP_SECRET"]
handle = "YOUR_HANDLE"
policy = json.dumps({
"call": ["read", "convert"],
"expiry": int(time.time()) + 300,
"handle": handle,
}, separators=(",", ":"))
encoded = base64.urlsafe_b64encode(policy.encode()).decode()
signature = hmac.new(APP_SECRET.encode(), encoded.encode(), hashlib.sha256).hexdigest()
url = (
"https://cdn.filestackcontent.com/"
f"security=policy:{encoded},signature:{signature}"
f"/doc_detection/{handle}"
)
with open("cleaned.jpg", "wb") as f:
f.write(requests.get(url).content)
FILESTACK_APP_SECRET comes from the Security section of your application in the developer portal, and handle is the file handle returned by the upload. A successful call writes a JPEG of the detected page to cleaned.jpg. Scoping the policy to a single handle, as above, keeps a leaked URL useless for anything else.
The included allowance is 1,000 images a month, and usage above that runs at $0.2 an image.
What the preprocess parameter changes
preprocess decides the finish. It defaults to true, which pushes the page to black text on white paper and drops it to greyscale. preprocess:false returns the same crop in RGB, with the shadow that fell across the desk still on it. The task takes one other parameter, coords, which defaults to false.
Both return 1021 x 749 from the same 1400 x 1050 source, so the crop and the straightening happen either way. The default suits archiving and a thumbnail of a stored document. preprocess:false is right when the reader is a person and the colour of the paper carries information, such as a stamp or a signature in blue ink.
The response is an image whichever way the detection goes. When there is no document in the frame, the task returns the whole frame binarised, so a 1200 x 800 photograph returns 1199 x 799. Compare the output dimensions with the source when you need to know whether a page was found. The parameter table and the workflow task configuration are in Document Detection.
Should you run document detection before OCR
No. ocr reads a photographed page directly, so point it at the uploaded handle. From the invoice photograph above, held at an angle, lit from one side and slightly out of focus, ocr reads 48 lines and all ten invoice fields.
| Call | Input | Lines read | Invoice fields |
|---|---|---|---|
ocr |
The uploaded photo, 1400 x 1050 | 48 | 10 of 10 |
ocr |
The doc_detection output, 1021 x 749 |
46 | 8 of 10 |
The ten fields are invoice number NGP-2026-04417, PO number WCG-88213, issue date 14 August 2026, due date 13 September 2026, terms Net 30, bill to Winterbourne Clinical Group, line amount 2,214.00, subtotal 4,060.00, sales tax 284.20 and total due 4,440.95. The binarised page is built for archiving and review, and ocr reads more from the original photograph.
So the two tasks answer different questions on the same upload. doc_detection produces the image, and ocr reads the handle that arrived. The invoice OCR API response nests text areas, lines and words, with a bounding box on every word. Both tasks sit inside a wider document extraction pipeline, with capture before them and validation after.
What the coords parameter returns
Adding coords:true switches the response from an image to JSON, with the position of the detected page at the top level of the response:
{"coords":{"height":1065,"width":1020,"x":160,"y":196}}
That is the response for the 1400 x 1050 invoice photograph above. width matches the width of the page image the task returns. To show or store the page itself, use the image the task returns, which is already cut to the page.
When document detection returns an error
A file stored with a non-image mimetype, such as multipart/form-data, returns HTTP 400 with the task named:
HTTP 400
We're encountering an error with doc_detection provider. Please connect with support.
Error: Invalid request params. Details: {"mimetype": "unsupported mimetype"} (failed task index 0)
The stored mimetype is set at upload, and a multipart form that does not set a content type on the file part stores the file as multipart/form-data. The metadata call shows the stored mimetype:
curl "https://cdn.filestackcontent.com/HANDLE/metadata?mimetype=true"
{"mimetype":"multipart/form-data"}
An image type there, such as image/png, and the task takes the file. Anything else, and re-uploading with the content type set on the file part is the fix.
The documented input limit is 2000 x 2000 pixels. Chain resize in front of the task to bring a larger image under it:
https://cdn.filestackcontent.com/security=policy:POLICY,signature:SIGNATURE/resize=width:1400/doc_detection/HANDLE
Tasks run left to right, so the resize happens first and doc_detection sees the smaller image. A 2400 x 1800 upload through that URL returns a 1020 x 750 page.
What document detection costs and how it caches
The response carries Cache-Control: public, and a repeat request to the same URL is served from cache, so repeat views come from the CDN rather than from another run of the task. The max-age follows the remaining life of the signing policy, so a short expiry gives a short cache window, on a URL that stops working at the same moment.
At $0.2 an image, one run per document is the shape to aim for. Add store to the chain and the cleaned page becomes a file of its own, with a handle you can serve, resize and archive without running the task again. The policy needs the store call alongside read and convert, so sign a new one:
policy = json.dumps({
"call": ["read", "convert", "store"],
"expiry": int(time.time()) + 300,
"handle": handle,
}, separators=(",", ":"))
encoded = base64.urlsafe_b64encode(policy.encode()).decode()
signature = hmac.new(APP_SECRET.encode(), encoded.encode(), hashlib.sha256).hexdigest()
url = (
"https://cdn.filestackcontent.com/"
f"security=policy:{encoded},signature:{signature}"
f"/doc_detection/store=filename:cleaned.jpg/{handle}"
)
stored = requests.get(url).json()
The response is JSON rather than an image, and it carries the handle of the stored page:
{
"filename": "cleaned.jpg",
"handle": "57KYHOHOTf2Wec2mRzQn",
"size": 431272,
"type": "image/jpeg",
"url": "https://cdn.filestackcontent.com/57KYHOHOTf2Wec2mRzQn"
}
Write stored["handle"] onto the document record next to the original. Requests for the stored file are ordinary delivery, so the archive is served without the task running again.
Where document detection belongs in an upload flow
Document Detection is available as a Workflow task under Intelligence, so it can run the moment the file lands rather than on first request. That fits mobile capture during onboarding, where a user photographs a licence or a utility bill and a reviewer opens it hours later. The stored page is what the reviewer sees.
Expense and receipt capture is the other shape. A receipt photographed on a restaurant table arrives with the same angle, background and uneven light as the invoice above, and document detection for receipts runs that expense flow end to end.
The task also corrects folded, rotated and wrinkled scans. If you are on Start, Grow or Scale, the task is answering now, and the integration is one path segment.
FAQ
Should I run document detection before OCR?
Not for text extraction. On the tested photograph ocr read all ten fields off the raw upload and only eight off the detected page, misreading the PO number and the payment terms. Point ocr at the uploaded handle and use doc_detection for the image a person will look at.
How do I know whether a page was actually found?
Compare the output dimensions with the source. A photograph with no document in the frame still returns an image, binarised at very close to the original size, because what came back is the frame rather than a page.
Why does my call return HTTP 400 with an unsupported mimetype?
The file was stored with a non-image type, usually multipart/form-data from a form that did not set a content type on the file part. Read the stored mimetype back from the metadata endpoint before investigating the URL, and re-upload with the type set.
What does coords:true give me?
JSON with the position of the detected page rather than an image, and width matches the width of the page the task returns. To show or store the page itself, use the image, which is already cut to the page.
Joshua is a web developer with over 4 years of experience building responsive, high-performance websites and web applications. Currently working as an AI Automation Specialist, he combines modern web development with automation to create efficient, scalable digital solutions. He shares practical insights on WordPress, web development, and emerging technologies.
Read More →