Bangun API Pendeteksi Gambar AI dengan C2PA + Klasifikasi Tutorial on building a FastAPI service with a POST /verify endpoint that combines two independent signals—C2PA cryptographic provenance manifests and an AI image detection classifier—to assess whether an uploaded image was generated by AI or captured by a camera. The service reads C2PA content credentials for cryptographically signed metadata, calls an AI detection API to analyze pixel-level features, and returns a JSON verdict with confidence levels. The tutorial also covers designing an OpenAPI contract, testing with Apidog, and using the c2pa-python library to handle C2PA manifest validation. Seseorang mengunggah foto ke produk Anda dan mengklaim foto itu diambil dengan kamera. Backend Anda tidak bisa lagi hanya “percaya pada mata”, karena generator gambar sekarang dapat menghasilkan gambar yang tampak nyata. Pendekatan yang lebih aman adalah menggabungkan dua sinyal independen: manifes asal-usul kriptografis dari C2PA dan skor pengklasifikasi deteksi gambar AI. Dalam tutorial ini, kita akan membangun layanan FastAPI dengan endpoint POST /verify . Endpoint ini menerima gambar, membaca Kredensial Konten C2PA jika tersedia, memanggil API deteksi gambar AI sebagai sinyal kedua, lalu mengembalikan putusan JSON. Karena ini proyek API, kita juga akan mendesain kontrak OpenAPI terlebih dahulu dan mengujinya dengan Apidog https://apidog.com?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . TL;DR Anda akan membuat layanan FastAPI yang: - menerima unggahan gambar lewat POST /verify , - membaca dan memvalidasi manifes C2PA dengan c2pa-python , - memanggil pengklasifikasi deteksi AI yang di-host, - menggabungkan dua sinyal menjadi salah satu putusan: likely authentic likely ai uncertain - mengembalikan skor kepercayaan dan detail sinyal mentah, - mendesain kontrak OpenAPI dan menjalankan mock/test endpoint dengan Apidog. Mengapa Menggunakan Dua Sinyal? Tidak ada satu properti file yang bisa membuktikan “gambar ini dibuat manusia” atau “gambar ini dibuat AI”. Yang tersedia adalah sinyal. 1. Sinyal asal-usul C2PA C2PA adalah standar terbuka untuk menyertakan metadata yang ditandatangani secara kriptografis pada file media. Metadata ini disebut manifes, dan nama yang biasa dilihat pengguna adalah Kredensial Konten. Jika kamera, editor, atau generator gambar mendukung C2PA, alat tersebut dapat menulis riwayat pembuatan atau pengeditan gambar dan menandatanganinya dengan sertifikat. Kelemahannya: - C2PA bersifat opt-in. - Screenshot biasanya menghapus metadata. - Aplikasi pesan atau platform upload sering menghapus metadata. - Tidak adanya manifes bukan berarti gambar palsu atau asli. 2. Sinyal pengklasifikasi AI Pengklasifikasi deteksi AI melihat piksel gambar dan mengembalikan kemungkinan bahwa gambar tersebut dihasilkan AI. Kelemahannya: - hasilnya probabilistik, - bisa false positive, - bisa false negative, - akurasi dapat turun pada gambar yang dikompresi berat atau berasal dari generator baru. Jadi strategi yang lebih jujur adalah: “Inilah yang bisa dibuktikan secara kriptografis, inilah perkiraan model, dan inilah tingkat keyakinan gabungannya.” Jika Anda ingin memahami mode kegagalan pendekatan satu sinyal, baca juga artikel tentang mengapa deteksi gambar AI gagal http://apidog.com/blog/why-ai-image-detection-fails?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . Arsitektur Layanan Layanan ini hanya memiliki satu endpoint dan dua sinyal downstream. ┌─────────────────────────────┐ gambar ──▶ │ FastAPI POST /verify │ │ │ │ 1. Validasi unggahan │ │ 2. Baca manifes C2PA │ │ 3. Panggil classifier AI │ │ 4. Gabungkan putusan │ └─────────────────────────────┘ │ ▼ JSON verdict + confidence Stack yang digunakan: - Python 3.10+ - FastAPI - Uvicorn python-multipart httpx c2pa-python Instal dependensi: pip install fastapi "uvicorn standard " python-multipart httpx c2pa-python Membaca Sinyal C2PA Pustaka c2pa-python adalah binding Python untuk pustaka Rust c2pa-rs . Kita akan menggunakannya untuk membaca manifes C2PA dari file gambar. Buat file provenance.py : python provenance.py import json import c2pa def read provenance image path: str - dict: """ Baca dan validasi manifes C2PA dari gambar. Mengembalikan dict yang dinormalisasi. """ try: with c2pa.Reader image path as reader: manifest store = json.loads reader.json except c2pa.C2paError as err: if str err .startswith "ManifestNotFound" : return { "has manifest": False, "validation": "none", "detail": "Tidak ada manifes C2PA di gambar ini.", } return { "has manifest": True, "validation": "error", "detail": f"Tidak dapat mengurai manifes: {err}", } active label = manifest store.get "active manifest" manifests = manifest store.get "manifests", {} active = manifests.get active label, {} validation status = manifest store.get "validation status", validation = "valid" if not validation status else "invalid" claim generator = active.get "claim generator", "unknown" signature issuer = active.get "signature info", {} .get "issuer", "unknown" return { "has manifest": True, "validation": validation, "claim generator": claim generator, "signature issuer": signature issuer, "validation status": validation status, "detail": "Manifes berhasil dibaca.", } Hal penting: - ManifestNotFound adalah kondisi normal. - Manifes hilang tidak boleh dianggap error. - validation status kosong berarti tanda tangan dan hash valid. - validation status berisi data berarti manifes gagal validasi. - claim generator dapat membantu mengidentifikasi apakah alat pembuatnya kamera, editor, atau generator AI. Memanggil Pengklasifikasi Deteksi AI Untuk sinyal kedua, gunakan API deteksi AI yang di-host. Contoh ini memakai Sightengine karena endpoint dan responsnya terdokumentasi dengan jelas. Polanya sama untuk vendor lain: ganti URL, parameter, dan field respons yang dibaca. Endpoint Sightengine: https://api.sightengine.com/1.0/check.json Buat file classifier.py : python classifier.py import httpx SIGHTENGINE URL = "https://api.sightengine.com/1.0/check.json" async def classify image image bytes: bytes, filename: str, api user: str, api secret: str, timeout seconds: float = 8.0, - dict: """ Kirim gambar ke detektor AI yang di-host. Mengembalikan skor AI-generated yang dinormalisasi. """ data = { "models": "genai", "api user": api user, "api secret": api secret, } files = { "media": filename, image bytes } try: async with httpx.AsyncClient timeout=timeout seconds as client: response = await client.post SIGHTENGINE URL, data=data, files=files, response.raise for status payload = response.json except httpx.TimeoutException: return { "available": False, "reason": "classifier timeout", } except httpx.HTTPStatusError as err: return { "available": False, "reason": f"classifier http {err.response.status code}", } except httpx.HTTPError as err: return { "available": False, "reason": f"classifier error: {err}", } if payload.get "status" = "success": return { "available": False, "reason": payload.get "error", {} .get "message", "unknown error" , } ai score = payload.get "type", {} .get "ai generated" if ai score is None: return { "available": False, "reason": "missing score in response", } return { "available": True, "ai score": float ai score , } Prinsip implementasinya: - Gunakan timeout eksplisit. - Jangan biarkan kegagalan vendor menjatuhkan endpoint. - Kembalikan available: false jika classifier timeout atau error. - Perlakukan skor sebagai probabilitas, bukan fakta. Untuk perbandingan vendor, lihat daftar API deteksi gambar AI terbaik http://apidog.com/blog/best-ai-image-detection-apis?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . Untuk pendekatan manual dan teknis lainnya, lihat panduan cara memeriksa apakah gambar dihasilkan oleh AI http://apidog.com/blog/how-to-check-if-an-image-is-ai-generated?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . Mendesain Kontrak POST /verify Sebelum menulis route FastAPI, desain kontrak endpoint. Ini membuat frontend bisa mulai integrasi lewat mock server sebelum backend selesai. Dengan Apidog https://apidog.com?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation , Anda bisa: - mendesain endpoint secara visual, - mengimpor/menulis OpenAPI, - membuat mock server, - menyimpan skenario test endpoint, - menjalankan test terhadap backend asli. Request Endpoint menerima multipart/form-data dengan satu field: image: file Response Contoh response: { "verdict": "likely ai", "confidence": 0.86, "signals": { "provenance": { "has manifest": true, "validation": "valid", "claim generator": "SomeImageTool/2.1", "signature issuer": "Some Issuing CA" }, "classifier": { "available": true, "ai score": 0.91 } }, "explanation": "Manifes C2PA yang valid menyebutkan alat gambar AI, dan pengklasifikasi menilai gambar tersebut kemungkinan dihasilkan AI.", "checked at": "2026-05-21T09:30:00Z" } Nilai verdict hanya boleh salah satu dari: likely authentic likely ai uncertain Gunakan uncertain ketika sinyal lemah, hilang, atau bertentangan. Skema OpenAPI Tambahkan komponen response berikut ke spesifikasi OpenAPI Anda: components: schemas: VerifyResponse: type: object required: - verdict - confidence - signals - checked at properties: verdict: type: string enum: - likely authentic - likely ai - uncertain confidence: type: number format: float minimum: 0 maximum: 1 signals: type: object properties: provenance: type: object properties: has manifest: type: boolean validation: type: string enum: - valid - invalid - error - none claim generator: type: string signature issuer: type: string classifier: type: object properties: available: type: boolean ai score: type: number format: float explanation: type: string checked at: type: string format: date-time Jika Anda ingin menerapkan workflow spec-first, lihat panduan mode spec-first di Apidog http://apidog.com/blog/spec-first-mode-apidog-beta-walkthrough?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . Menggabungkan Dua Sinyal Buat file verdict.py . Logika berikut konservatif: - manifes C2PA valid lebih kuat daripada classifier, - manifes gagal validasi menghasilkan uncertain , - classifier digunakan saat manifes tidak tersedia, - konflik sinyal menghasilkan uncertain . php verdict.py def combine signals provenance: dict, classifier: dict - dict: """ Gabungkan sinyal asal-usul dan classifier menjadi satu putusan. """ has manifest = provenance.get "has manifest", False validation = provenance.get "validation", "none" generator = provenance.get "claim generator" or "" .lower classifier ok = classifier.get "available", False ai score = classifier.get "ai score" ai keywords = "firefly", "dall-e", "dalle", "midjourney", "stable", "gpt", "gemini", "imagen", "generat", generator looks ai = any keyword in generator for keyword in ai keywords 1. Manifes valid menyebut alat AI. if has manifest and validation == "valid" and generator looks ai: return verdict "likely ai", 0.95, "Manifes C2PA yang valid menyebutkan alat gambar AI.", 2. Manifes valid dari alat non-AI. if has manifest and validation == "valid" and not generator looks ai: if classifier ok and ai score is not None and ai score 0.85: return verdict "uncertain", 0.55, "Manifes terlihat otentik tetapi pengklasifikasi tidak setuju; sinyal bertentangan.", return verdict "likely authentic", 0.9, "Manifes C2PA yang valid dari alat non-AI tersedia.", 3. Manifes ada tetapi gagal validasi. if has manifest and validation in "invalid", "error" : return verdict "uncertain", 0.6, "Gambar membawa manifes C2PA yang gagal divalidasi.", 4. Tidak ada manifes, gunakan classifier. if classifier ok and ai score is not None: if ai score = 0.7: return verdict "likely ai", round ai score, 2 , "Tidak ada data asal-usul; pengklasifikasi menilai gambar kemungkinan dihasilkan AI.", if ai score <= 0.3: return verdict "likely authentic", round 1 - ai score, 2 , "Tidak ada data asal-usul; pengklasifikasi menilai gambar kemungkinan otentik.", return verdict "uncertain", 0.5, "Tidak ada data asal-usul dan skor pengklasifikasi tidak meyakinkan.", 5. Tidak ada manifes dan classifier tidak tersedia. return verdict "uncertain", 0.0, "Tidak ada data asal-usul dan pengklasifikasi tidak tersedia.", def verdict verdict: str, confidence: float, explanation: str - dict: return { "verdict": verdict, "confidence": confidence, "explanation": explanation, } Ambang batas 0.7 , 0.3 , dan 0.85 bukan angka universal. Sesuaikan berdasarkan toleransi risiko produk Anda. Membuat Aplikasi FastAPI Buat file main.py : python main.py import os import tempfile from datetime import datetime, timezone from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.responses import JSONResponse from provenance import read provenance from classifier import classify image from verdict import combine signals app = FastAPI title="API Detektor Gambar AI", version="1.0.0", ALLOWED TYPES = { "image/jpeg", "image/png", "image/webp", } MAX BYTES = 12 1024 1024 12 MB SIGHTENGINE USER = os.environ.get "SIGHTENGINE API USER", "" SIGHTENGINE SECRET = os.environ.get "SIGHTENGINE API SECRET", "" @app.post "/verify" async def verify image: UploadFile = File ... : 1. Validasi content type. if image.content type not in ALLOWED TYPES: raise HTTPException status code=415, detail= f"Tipe {image.content type} tidak didukung. " "Kirim JPEG, PNG, atau WebP." , 2. Baca byte gambar. image bytes = await image.read if len image bytes == 0: raise HTTPException status code=400, detail="File kosong.", if len image bytes MAX BYTES: raise HTTPException status code=413, detail="File melebihi batas 12 MB.", 3. Baca C2PA. c2pa.Reader membutuhkan path file, jadi gunakan file sementara. suffix = os.path.splitext image.filename or "" 1 or ".img" with tempfile.NamedTemporaryFile suffix=suffix, delete=False as tmp: tmp.write image bytes tmp path = tmp.name try: provenance = read provenance tmp path finally: os.unlink tmp path 4. Panggil classifier jika credential tersedia. if SIGHTENGINE USER and SIGHTENGINE SECRET: classifier = await classify image image bytes=image bytes, filename=image.filename or "upload", api user=SIGHTENGINE USER, api secret=SIGHTENGINE SECRET, else: classifier = { "available": False, "reason": "classifier not configured", } 5. Gabungkan sinyal. result = combine signals provenance, classifier return JSONResponse { "verdict": result "verdict" , "confidence": result "confidence" , "signals": { "provenance": { key: provenance.get key for key in "has manifest", "validation", "claim generator", "signature issuer", }, "classifier": { "available": classifier.get "available", False , "ai score": classifier.get "ai score" , }, }, "explanation": result "explanation" , "checked at": datetime.now timezone.utc .isoformat , } Jalankan server lokal: uvicorn main:app --reload Endpoint aktif di: http://127.0.0.1:8000/verify Contoh request dengan curl : curl -X POST http://127.0.0.1:8000/verify \ -F "image=@sample.jpg" Jika Anda menggunakan Sightengine, set environment variable terlebih dahulu: export SIGHTENGINE API USER="your user" export SIGHTENGINE API SECRET="your secret" Desain seperti ini cocok untuk layanan API kecil yang fokus pada satu kapabilitas. Untuk konteks lebih luas tentang pola produk berbasis API, baca artikel tentang software yang menjadi headless http://apidog.com/blog/software-going-headless-api-is-product?utm source=dev.to&utm medium=wanda&utm content=n8n-post-automation . Mocking dan Testing dengan Apidog Frontend tidak perlu menunggu backend selesai. Setelah kontrak OpenAPI tersedia, Anda bisa membuat mock server di Apidog. 1. Buat mock server Di Apidog: - Buat project baru. - Tambahkan endpoint POST /verify . - Set body sebagai multipart/form-data . - Tambahkan field image bertipe file. - Tambahkan response schema VerifyResponse . - Buat contoh response untuk beberapa skenario. Contoh skenario mock yang sebaiknya dibuat: - likely authentic dengan manifes kamera valid, - likely ai dengan manifes dari alat AI, - uncertain ketika classifier tidak tersedia, - error 415 untuk tipe file tidak didukung, - error 413 untuk file terlalu besar. Frontend dapat mengarahkan fetch ke URL mock Apidog. Saat backend asli siap, ganti base URL saja. 2. Jalankan test endpoint Setelah backend lokal berjalan: - Buat request POST /verify di Apidog. - Set URL ke http://127.0.0.1:8000/verify . - Di Body, pilih form-data . - Tambahkan field image . - Set tipe field ke File . - Pilih gambar uji. - Kirim request. Tambahkan assertion: - status response adalah 200 , - verdict ada, - verdict adalah salah satu dari likely authentic , likely ai , uncertain , - confidence angka antara 0 dan 1 , - signals.provenance.has manifest bertipe boolean, - signals.classifier.available bertipe boolean. Buat test suite kecil dengan beberapa file: - gambar dengan Kredensial Konten, - JPEG biasa tanpa manifes, - file terlalu besar, - file non-gambar yang diganti ekstensi menjadi .jpg , - gambar yang memicu timeout/mock classifier unavailable. Dengan cara ini, perubahan pada fungsi putusan bisa langsung diuji ulang. Penguatan Implementasi Endpoint verifikasi menerima input yang bisa bersifat adversarial. Tambahkan perlindungan berikut sebelum production. Validasi file sebenarnya Content-Type bisa dipalsukan. Untuk validasi lebih kuat, decode gambar dengan Pillow: pip install pillow Contoh validasi: python from io import BytesIO from PIL import Image, UnidentifiedImageError def validate image bytes image bytes: bytes - None: try: with Image.open BytesIO image bytes as img: img.verify except UnidentifiedImageError: raise ValueError "File bukan gambar valid." Lalu panggil sebelum membaca C2PA atau classifier. Jangan anggap manifes hilang sebagai error Kasus paling umum adalah gambar tanpa manifes. Ini bukan 500 , bukan bukti palsu, dan bukan bukti asli. Respons yang benar biasanya tetap 200 dengan sinyal: { "has manifest": false, "validation": "none" } Tangani timeout classifier Classifier adalah dependency jaringan. Gunakan timeout pendek dan perlakukan kegagalan sebagai sinyal tidak tersedia. { "available": false, "reason": "classifier timeout" } Jangan biarkan vendor yang lambat menjatuhkan endpoint Anda. Waspadai manifes palsu atau rusak Manifes yang ada belum tentu valid. Selalu cek validation status . - kosong: valid, - berisi data: gagal validasi. Manifes gagal validasi harus menghasilkan uncertain , bukan likely authentic . Batasi ukuran file Contoh kode memakai batas 12 MB. Untuk production: - batasi ukuran request di reverse proxy, - batasi ukuran di aplikasi, - gunakan rate limiting, - logging minimal, - hindari menyimpan gambar pengguna lebih lama dari yang dibutuhkan. Perhatikan privasi Anda menerima gambar pengguna dan mungkin mengirimkannya ke vendor pihak ketiga. Pastikan: - tidak mencatat byte gambar, - file sementara dihapus, - kebijakan privasi menjelaskan pemrosesan pihak ketiga, - penggunaan vendor sesuai dengan kebutuhan produk Anda. Apa yang Ditangkap dan Dilewatkan Tiap Sinyal | Skenario | Sinyal asal-usul C2PA | Sinyal pengklasifikasi | |---|---|---| | Gambar AI dari alat yang menulis Kredensial Konten | Menangkapnya: manifes menyebutkan generator | Biasanya menangkapnya: artefak visual hadir | | Gambar AI dengan metadata dihapus | Melewatkannya: tidak ada manifes | Menangkapnya: bekerja pada piksel | | Foto asli dari kamera yang menandatangani Kredensial Konten | Memverifikasi: manifes valid dan generator non-AI | Bisa false positive pada kompresi/edit berat | | Foto asli tanpa metadata | Tidak ada sinyal | Hanya estimasi probabilistik | | Gambar dengan manifes palsu atau dirusak | Menangkapnya lewat validation status | Mungkin menangkap, mungkin tidak | | Generator baru yang belum ada di data training | Menangkap hanya jika alat menulis manifes | Bisa melewatkan karena out-of-distribution | | Foto asli dengan retouch AI | Jika ada, manifes mencatat riwayat edit | Ambigu; skor bisa berada di tengah | Kesimpulannya: C2PA kuat tetapi tidak selalu ada. Classifier selalu bisa dijalankan pada piksel, tetapi hasilnya tidak pasti. Gabungan keduanya lebih berguna daripada salah satu saja. Kasus Penggunaan Pola POST /verify ini cocok untuk: Platform konten buatan pengguna Tandai gambar yang kemungkinan AI atau memiliki manifes gagal validasi. Ruang berita dan fact-checking Berikan editor sinyal asal-usul dan skor classifier dalam satu respons. Asuransi dan klaim Flag bukti foto yang terlihat sintetis atau memiliki metadata rusak. Pipeline aset internal Cegah gambar AI masuk ke library tanpa label. CMS sadar provenance Tampilkan badge terverifikasi ketika Kredensial Konten valid tersedia. Kesimpulan Deteksi gambar AI yang baik bukan tentang menemukan satu tes sempurna. Yang lebih realistis adalah menggabungkan sinyal independen dan menyatakan ketidakpastian secara eksplisit. Ringkasnya: - C2PA memberi sinyal kriptografis yang kuat, tetapi sering tidak tersedia. - Classifier memberi sinyal universal, tetapi probabilistik. - FastAPI cukup untuk membangun layanan POST /verify kecil dan fokus. - Putusan tiga nilai lebih jujur daripada boolean. - OpenAPI + Apidog membantu frontend dan backend bekerja paralel lewat mock server dan test suite. Langkah berikutnya: desain skema /verify , buat mock server di Apidog, jalankan test endpoint, lalu ganti mock URL ke backend asli saat implementasi siap.