Foto paspor adalah file paling sensitif yang pernah diterima kebanyakan aplikasi. Di dalamnya ada wajah, nama lengkap, tanggal lahir, dan nomor dokumen, dan itu sudah cukup untuk membuka akun di tempat lain. Namun di banyak alur unggah, gambar itu disalin lima atau enam kali sebelum ada yang bertanya apakah gambar itu memang perlu ada.

Tulisan ini melihat pertanyaan tersebut dari sisi engineering. Tulisan ini mengutip apa yang dikatakan GDPR tentang penyimpanan data, menelusuri tempat-tempat di mana gambar paspor diam-diam menumpuk, dan menjelaskan pola yang kami sebut baca, kembalikan, lupakan: gambar dibaca, hasilnya dikembalikan, dan tidak ada bagian gambar yang disimpan. Di bagian akhir ada hal yang tetap menjadi tugas Anda, karena sebagian bisnis wajib menyimpan salinan, dan mulai Juli 2027 UE menuliskannya secara jelas dalam undang-undang baru.

Ini adalah blog doc.cheap, sebuah API OCR paspor dan kartu identitas yang membaca dokumen identitas dan mengembalikan hasilnya sebagai JSON. Baca bagian-bagian tentang produk dengan mengingat hal itu.

Apa yang sebenarnya diminta GDPR

GDPR tidak mengatakan "jangan pernah menyimpan gambar paspor". GDPR mengatakan sesuatu yang lebih berguna: simpan apa yang Anda butuhkan, selama Anda membutuhkannya, dan tidak lebih lama. Pasal 5 ayat (1) Reg. (EU) 2016/679 menetapkan prinsip-prinsipnya. Dua di antaranya menentukan sebagian besar desain.

Prinsip Teks Pasal 5 ayat (1) (versi bahasa Inggris) Artinya bagi sebuah gambar
Minimisasi data, huruf (c) "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed" Jika proses Anda membutuhkan nama, tanggal lahir, dan nomor dokumen, gambarnya sendiri mungkin tidak lagi diperlukan setelah semua itu terbaca.
Pembatasan penyimpanan, huruf (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" Setiap salinan butuh tanggal akhir, dan "kami belum sempat menghapusnya" bukanlah tanggal akhir.

Pasal 5 ayat (2) menambahkan akuntabilitas: Anda harus bisa menunjukkan bahwa Anda mengikuti prinsip-prinsip ini. Dan Pasal 25 ayat (1), tentang perlindungan data sejak perancangan, meminta "appropriate technical and organisational measures, such as pseudonymisation, which are designed to implement data-protection principles, such as data minimisation, in an effective manner", yaitu langkah teknis dan organisasi yang sesuai, misalnya pseudonimisasi, yang dibuat untuk menerapkan prinsip seperti minimisasi data secara efektif.

Jika dibaca bersama, keduanya mengubah pertanyaan hukum menjadi pertanyaan engineering. Makin sedikit salinan sebuah gambar, makin sedikit tempat yang harus Anda jelaskan, amankan, backup, dan pada akhirnya kosongkan. Salinan yang tidak pernah ditulis adalah satu-satunya salinan yang tidak memerlukan semua pekerjaan itu.

Ke mana gambar paspor berakhir

Kebanyakan tim sengaja menyimpan gambar di satu tempat. Masalahnya ada di tempat-tempat yang tidak dipilih siapa pun. Berikut daftar untuk dicocokkan dengan alur Anda sendiri.

Tempat Bagaimana gambar sampai ke sana
Bucket unggahan Klien mengunggah ke object storage terlebih dahulu, lalu backend membacanya dari sana. Objeknya bertahan lebih lama dari request.
Log request Middleware logging menulis body request, dan gambar base64 adalah body request.
Laporan error Pelacak exception melampirkan payload dari request yang gagal.
Antrean dan retry Pesan job membawa gambar, dan dead-letter queue menyimpan yang gagal selama berminggu-minggu.
Backup dan snapshot Snapshot database atau disk yang diambil hari itu menyimpan setiap gambar yang ditulis sebelumnya, lama setelah barisnya dihapus.
Tiket dukungan Pengguna mengirim ulang foto lewat email "karena unggahannya tidak berhasil".
Analitik dan session replay Sebuah alat merekam halaman, termasuk pratinjau file yang dipilih.
Penyedia OCR Layanan yang membaca dokumen menyimpan salinannya sendiri, dengan aturan retensinya sendiri.

Baris terakhir adalah yang paling sedikit Anda kendalikan. Log Anda sendiri bisa Anda perbaiki. Salinan yang dipegang vendor tunduk pada pengaturan vendor itu, dan Anda harus menanyakan seperti apa pengaturannya.

Polanya: baca, kembalikan, lupakan

Polanya mudah dirumuskan. Gambar hanya ada di memori, selama satu request. Yang keluar dari request adalah hasil bacaan: nilai-nilai yang diekstrak. Gambarnya sama sekali tidak keluar.

  1. Kirim gambar langsung ke pengenalan. Tanpa bucket unggahan di tengah. Jika bucket tidak bisa dihindari untuk file besar, beri objek itu masa hidup beberapa menit dan hapus saat panggilan kembali.
  2. Gunakan apa yang Anda butuhkan dari respons saat itu juga. Potongan gambar seperti foto pemegang hanya ada di respons tersebut. Jika alur Anda membandingkan selfie dengan potret, lakukan sekarang.
  3. Simpan hasil bacaan, bukan gambarnya. Simpan field yang dibutuhkan proses Anda, dengan aturan retensi Anda sendiri. Tanggal lahir dan nomor dokumen tetap data pribadi, jadi keduanya juga mendapat tanggal akhir.
  4. Jauhkan gambar dari log dan laporan error. Buang body di batas sistem, di satu tempat, alih-alih berharap setiap pemanggil ingat.
  5. Catat hasilnya. Untuk setiap tempat di tabel di atas, catat apakah gambar bisa sampai ke sana dan mengapa tidak. Catatan itulah akuntabilitas yang diminta Pasal 5 ayat (2).

Apa yang dilakukan API kami dengan gambar

Begini cara doc.cheap menangani pertanyaan yang sama, seperti dijelaskan di halaman retensi data dan privasi.

  • Gambar tidak pernah disimpan. Gambar berada di memori selama request, diserahkan ke mesin pengenalan, dan hilang saat respons ditulis. Tidak ada disk, object store, atau log yang menerimanya.
  • Potongan gambar juga tidak disimpan. Potongan dokumen, foto pemegang, dan tanda tangan kembali di respons panggilan yang membuatnya. Scan yang dibaca ulang nanti lewat GET /v1/scans/{id} memiliki semua slot gambar bernilai null.
  • Yang bisa disimpan adalah hasil bacaan, dan hanya selama jangka waktu yang Anda minta. Opsi retain_hours mengaturnya per request, dari 0 sampai 8760 jam (satu tahun). Nilai yang ditulis secara eksplisit selalu mengalahkan pengaturan akun.
  • retain_hours: 0 tidak menulis apa pun. Bukan baris yang langsung kedaluwarsa: tidak ada baris sama sekali. Tidak ada yang perlu dibersihkan, tidak ada apa pun di backup, dan tidak ada yang bisa diekspor. Scan itu tetap dihitung sebagai scan.
  • Default akun mencakup sisanya. Jika request tidak menyebut jangka waktu, pengaturan riwayat akun itu sendiri yang berlaku: 24 jam, 7 hari, 1 bulan, atau 1 tahun. Akun baru dimulai dengan 1 tahun, agar dasbor menampilkan riwayat. Memperpendek pengaturan berlaku juga untuk baris yang sudah tersimpan, masing-masing dihitung dari waktu pembuatannya sendiri.
  • Baris yang disimpan menyimpan satu gambar kecil: thumbnail berukuran paling besar 96 px di sisi terpanjangnya dan paling besar 16 KiB, ditampilkan di log operasi dasbor agar baris bisa dikenali. Thumbnail ini tidak bisa dibaca lewat API. Thumbnail ikut hilang saat barisnya hilang.
  • Satu scan bisa dihapus lebih awal. Kunci live mengirim DELETE /v1/scans/{id}, yang menghapus hasil, baris riwayat, dan thumbnail. Penghapusan ini permanen.

Panduan mengontrol retensi riwayat membahas pengaturannya langkah demi langkah, dan halaman kami tentang cara kami memproses data memberi ringkasannya.

Berikut panggilan tanpa retensi di Python dengan requests. Kunci sandbox publik sk_sandbox_public tercantum di dokumentasi dan tidak perlu mendaftar: kunci ini memberi total 10 dokumen yang dikenali gratis per alamat, dan paling banyak 10 request per jam. Retensi nol adalah pengaturan akun Anda sendiri, jadi perlu kunci live Anda. Sandbox publik bukan akun: sandbox menyimpan catatan setiap scan, beserta gambar kecilnya, untuk log layanan itu sendiri, jadi kirimkan gambar uji, jangan pernah dokumen asli.

import base64
import uuid

import requests

API = "https://api.doc.cheap/v1/scans"
KEY = "sk_sandbox_public"  # kunci live Anda sendiri di produksi


def read_and_forget(path):
    with open(path, "rb") as f:
        image = base64.b64encode(f.read()).decode("ascii")
    response = requests.post(
        API,
        headers={
            "Authorization": f"Bearer {KEY}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "image": image,
            # 0: dengan kunci live, tidak ada apa pun tentang scan ini yang dicatat di sisi API.
            # False: tanpa potongan potret, karena alur ini tidak memakainya.
            "options": {"retain_hours": 0, "return_portrait": False},
        },
        timeout=30,
    )
    response.raise_for_status()
    scan = response.json()
    del image  # salinan lokal dibuang begitu panggilan kembali
    if scan["meta"]["status"] != "recognized":
        return None
    # Simpan hasil bacaan yang dibutuhkan proses Anda, dengan aturan retensi Anda sendiri.
    return {
        "scan_id": scan["meta"]["id"],
        "document_number": scan["document"]["number"],
        "expiry_date": scan["document"]["expiry_date"],
        "birth_date": scan["holder"]["birth_date"],
        "mrz_status": scan["mrz"]["status"],
    }

Retensi nol punya satu konsekuensi yang sebaiknya Anda ketahui sebelum mengejutkan Anda. Biasanya Idempotency-Key membuat retry mengembalikan hasil pertama. Dengan retain_hours: 0 tidak ada hasil tersimpan untuk dikembalikan, jadi selama 24 jam retry dengan kunci yang sama ditolak dengan HTTP 409 dan kode idempotency_replay_unavailable, alih-alih dijawab dua kali. Anggap jawaban itu sebagai "panggilan pertama sudah berhasil" dan gunakan hasil yang sudah Anda pegang.

Membaca ulang scan menunjukkan sisi lain dari desain ini. Kunci sandbox sama sekali tidak bisa membaca ulang apa pun, apa pun id-nya. Kami mengirim request ini dengan sk_sandbox_public pada 5 Oktober 2026:

curl https://api.doc.cheap/v1/scans/<SCAN_ID> \
  -H "Authorization: Bearer sk_sandbox_public"

Jawabannya HTTP 404 (pesannya dipotong):

{
  "error": {
    "code": "not_found",
    "message": "No scan with id …",
    "docs_url": "https://doc.cheap/docs/errors/not_found"
  }
}

Kunci live mendapat 404 yang sama untuk scan yang dibuat dengan retain_hours: 0, dan untuk scan mana pun setelah jangka waktunya lewat. Jika Anda membandingkan layanan dalam hal ini, perbandingan API OCR paspor bisa menjadi titik awal; tanyakan kepada masing-masing ke mana gambarnya pergi, bukan hanya apa yang dikembalikannya.

Apa yang tetap menjadi tugas Anda

Baca, kembalikan, lupakan menghilangkan salinan di sisi API. Pola ini tidak menentukan apa yang wajib disimpan bisnis Anda. Bagi sebagian bisnis jawabannya adalah "sebuah salinan", dan hukum menyatakannya.

Undang-undang anti pencucian uang UE yang baru, Reg. (EU) 2024/1624, berlaku mulai 10 Juli 2027. Pasal 90 menyatakan: "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." Artinya, aturan ini berlaku mulai 10 Juli 2027, kecuali bagi entitas wajib dalam Pasal 3 angka (3) huruf (n) dan (o), yang baru terkena mulai 10 Juli 2029. Pasal 77 tentang retensi catatan mewajibkan entitas wajib, seperti bank dan perusahaan keuangan lainnya, menyimpan:

"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;"

Dengan kata lain, salinan dokumen dan informasi yang diperoleh saat uji tuntas nasabah, termasuk informasi yang diperoleh lewat sarana identifikasi elektronik.

Pasal 77 ayat (3) menetapkan lamanya: catatan "retained for a period of 5 years commencing on the date of the termination of the business relationship", yaitu disimpan 5 tahun sejak hubungan usaha berakhir, dan setelah itu "obliged entities shall delete personal data upon expiry of the five-year period", artinya data pribadi harus dihapus begitu lima tahun itu habis. Pasal 77 ayat (2) mengizinkan, dengan syarat tertentu, "a retention of the references to such information" sebagai pengganti salinan, yaitu hanya menyimpan referensi ke informasi tersebut.

Jadi jika Anda adalah entitas wajib, retensi nol di API tidak menghapus kewajiban Anda untuk menyimpan catatan. Yang berubah adalah tempat catatan itu berada. Penyimpanan Anda sendiri menjadi satu-satunya salinan, dan prinsip pembatasan penyimpanan di atas tetap berlaku untuknya: lima tahun setelah hubungan berakhir, catatan itu dihapus. Pekerjaan desainnya adalah membuat penyimpanan itu disengaja, dengan satu tempat, satu pemilik, enkripsi, kontrol akses, dan job penghapusan, bukan tumpukan tak disengaja seperti di tabel di atas.

Jika Anda bukan entitas wajib, ajukan dulu pertanyaan sederhananya: apakah ada bagian dari proses Anda yang membutuhkan gambar setelah field-nya terbaca? Sering kali jawaban jujurnya adalah tidak.

Daftar periksa

  • Setiap tempat di tabel "ke mana gambar berakhir" sudah dicocokkan dengan alur Anda.
  • Gambar langsung dikirim ke pengenalan, atau lewat bucket dengan masa hidup beberapa menit.
  • Potongan gambar dipakai di dalam handler respons dan tidak ditulis ke mana pun.
  • Panggilan OCR mengatur retensinya dengan sengaja, retain_hours: 0 jika tidak ada yang perlu dibaca ulang.
  • Retry menangani jawaban 409 idempotency_replay_unavailable.
  • Log dan laporan error membuang body request di satu batas.
  • Field yang Anda simpan punya tanggal akhir, dan ada sesuatu yang menghapusnya.
  • Jika hukum mewajibkan salinan, salinan itu berada di satu penyimpanan yang disengaja dengan tanggal penghapusannya sendiri.

Ini adalah ringkasan engineering, bukan nasihat hukum. Jika Anda menemukan tempat di mana gambar bisa bocor yang terlewat oleh tulisan ini, kirim email ke admin@doc.cheap.

Pertanyaan untuk Anda: di mana terakhir kali Anda menemukan salinan dokumen identitas yang tidak pernah dimaksudkan untuk disimpan siapa pun? Ceritakan di kolom komentar di bawah.