Read, return, forget: processing passport scans without storing the image
What the GDPR asks of a passport image, where copies leak in a typical upload flow, and a read, return, forget pattern that keeps none on the API side.
Originally published at Read, return, forget: processing passport scans without storing the image
A passport photo is the most sensitive file most apps ever receive. It carries a face, a full name, a date of birth and a document number, and it is enough to open an account somewhere else. Yet in many upload flows the picture is copied five or six times before anyone asks whether it needs to exist at all.
This post looks at the question from the engineering side: what the GDPR says about keeping data, the places where a passport image quietly piles up, and a pattern called read, return, forget – the image is read, the result is returned, and nothing of the picture is kept. It ends with the part that stays your job, because some businesses are required to keep a copy, and from July 2027 a new EU law spells that out.
What the GDPR actually asks
The GDPR does not say "never store a passport image". It says something more useful: keep what you need, for as long as you need it, and no longer. Art. 5(1) of Reg. (EU) 2016/679 sets the principles. Two of them decide most of the design.
| Principle | Text of Art. 5(1) | What it means for an image |
|---|---|---|
| Data minimisation, point (c) | "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed" | If your process needs the name, date of birth and document number, the picture itself may not be necessary once they are read. |
| Storage limitation, point (e) | "kept in a form which permits identification of data subjects for no longer than is necessary for the purposes for which the personal data are processed" | Every copy needs an end date, and "we never got round to deleting it" is not one. |
Art. 5(2) adds accountability: you must be able to show that you follow these principles. And Art. 25(1), data protection by design, asks for "appropriate technical and organisational measures, such as pseudonymisation, which are designed to implement data-protection principles, such as data minimisation, in an effective manner".
Read together, they turn a legal question into an engineering one. The fewer copies of an image exist, the fewer places you have to describe, secure, back up and eventually empty. A copy that was never written is the only one that needs none of that work.
Where passport images end up
Most teams store the image on purpose in one place. The trouble is the places nobody chose. Walk through this list against your own flow.
| Place | How the image gets there |
|---|---|
| Upload bucket | The client uploads to object storage first, and the backend reads it from there. The object outlives the request. |
| Request logs | A logging middleware writes request bodies, and a base64 image is a request body. |
| Error reports | An exception tracker attaches the payload of the request that failed. |
| Queues and retries | A job message carries the image, and a dead-letter queue keeps the failed ones for weeks. |
| Backups and snapshots | A database or disk snapshot taken that day holds every image written before it, long after the row is deleted. |
| Support tickets | A user mails the photo again "because the upload did not work". |
| Analytics and session replay | A tool records the page, including the preview of the selected file. |
| The OCR provider | The service that reads the document keeps its own copy, under its own retention rules. |
The last row is the one you control least. Your own logs you can fix. A copy held by a vendor is governed by that vendor's settings, and you have to ask what they are.
The pattern: read, return, forget
The image exists only in memory, for the length of one request. What leaves the request is the reading: the extracted values. The picture does not leave at all.
- Send the image straight to recognition. No upload bucket in between. If a bucket is unavoidable for large files, give the object a lifetime of minutes and delete it when the call returns.
- Use what you need from the response at once. Crops such as the holder's photo exist only in that response. If your flow compares a selfie to the portrait, do it now.
- Keep the reading, not the picture. Store the fields your process needs, under your own retention rule. A date of birth and a document number are still personal data, so they get an end date too.
- Keep the image out of logs and error reports. Strip bodies at the boundary, in one place, rather than trusting every caller to remember.
- Write down the result. For each place in the table above, note whether the image can reach it and why not. That note is the accountability Art. 5(2) asks for.
Questions to ask your OCR provider
Because the last row of the table is the vendor's, these are the questions worth putting to any service that reads documents for you:
- Is the uploaded image written anywhere – disk, object store, logs – or does it live only in memory for the request?
- Are the crops (the holder's photo, the signature, the page crop) stored, or returned once in the response?
- Can retention be set per request, and does "zero" mean no row at all, or a row that expires soon?
- What does the account default keep, and does shortening it apply to rows already stored?
- Can one result be deleted early through the API?
- What does a retry look like with zero retention? If nothing is stored, an idempotent retry has no first result to return; the service should say so explicitly (for example with a 409) rather than quietly running the scan twice.
For one worked set of answers, the doc.cheap data retention and privacy page describes how that API treats each of these points: the image held in memory for the request only, crops returned once, a per-request window from zero (no row written) up to one year, and early deletion of a single result.
What stays your job
Read, return, forget removes the copies on the provider's side. It does not decide what your business has to keep. For some businesses the answer is "a copy", and the law says so.
The EU's new anti-money-laundering law, Reg. (EU) 2024/1624, applies from 10 July 2027. Art. 90 says: "It shall apply from 10 July 2027, except in relation to obliged entities referred to in Article 3, points (3)(n) and (o), to which it shall apply from 10 July 2029." Its Art. 77 on record retention makes obliged entities, such as banks and other financial firms, keep:
"a copy of the documents and information obtained in the performance of customer due diligence pursuant to Chapter III, including information obtained through electronic identification means;"
Art. 77(3) sets the length: the records are "retained for a period of 5 years commencing on the date of the termination of the business relationship", and then "obliged entities shall delete personal data upon expiry of the five-year period". Art. 77(2) allows, under conditions, "a retention of the references to such information" in place of copies.
So if you are an obliged entity, zero retention at the OCR step does not remove your duty to keep a record. It changes where the record lives. Your own store becomes the one copy, and the storage limitation principle still applies to it: five years after the relationship ends, it goes. The design work is to make that store deliberate – one place, one owner, encryption, access control and a deletion job – rather than the accidental pile from the table above.
If you are not an obliged entity, ask the plain question first: does anything in your process need the picture after the fields are read? Often the honest answer is no.
A checklist
- Every place in the "where images end up" table is checked against your flow.
- The image goes straight to recognition, or through a bucket with a lifetime of minutes.
- Crops are used inside the response handler and not written anywhere.
- The OCR call sets its retention on purpose, zero when nothing needs reading back.
- Retries handle the "nothing to replay" answer of a zero-retention call.
- Logs and error reports strip request bodies at one boundary.
- The fields you keep have an end date, and something deletes them.
- If a law requires a copy, it lives in one deliberate store with its own deletion date.
This is an engineering summary, not legal advice. The original post on doc.cheap also shows a zero-retention call in Python.
A question for you: where did you last find a copy of an identity document that nobody meant to keep? Tell me in the comments.
About the author
- doc.cheap reads passports, ID cards and driver's licences from a photo and returns JSON in one HTTP call. Free – 100 documents every month, then 1¢ per recognised document; failed scans are free. Try it with the public sandbox key sk_sandbox_public, no signup. There is also an MCP server for agents: https://doc.cheap/mcp
Comments
Join the discussionNo comments yet. Be the first to comment.

