{"slug": "bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi", "title": "Bangun API Pendeteksi Gambar AI dengan C2PA + Klasifikasi", "summary": "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.", "body_md": "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.\n\nDalam tutorial ini, kita akan membangun layanan FastAPI dengan endpoint `POST /verify`\n\n. 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).\n\n## TL;DR\n\nAnda akan membuat layanan FastAPI yang:\n\n- menerima unggahan gambar lewat\n`POST /verify`\n\n, - membaca dan memvalidasi manifes C2PA dengan\n`c2pa-python`\n\n, - memanggil pengklasifikasi deteksi AI yang di-host,\n- menggabungkan dua sinyal menjadi salah satu putusan:\n`likely_authentic`\n\n`likely_ai`\n\n`uncertain`\n\n- mengembalikan skor kepercayaan dan detail sinyal mentah,\n- mendesain kontrak OpenAPI dan menjalankan mock/test endpoint dengan Apidog.\n\n## Mengapa Menggunakan Dua Sinyal?\n\nTidak ada satu properti file yang bisa membuktikan “gambar ini dibuat manusia” atau “gambar ini dibuat AI”. Yang tersedia adalah sinyal.\n\n### 1. Sinyal asal-usul C2PA\n\nC2PA 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.\n\nJika kamera, editor, atau generator gambar mendukung C2PA, alat tersebut dapat menulis riwayat pembuatan atau pengeditan gambar dan menandatanganinya dengan sertifikat.\n\nKelemahannya:\n\n- C2PA bersifat opt-in.\n- Screenshot biasanya menghapus metadata.\n- Aplikasi pesan atau platform upload sering menghapus metadata.\n- Tidak adanya manifes bukan berarti gambar palsu atau asli.\n\n### 2. Sinyal pengklasifikasi AI\n\nPengklasifikasi deteksi AI melihat piksel gambar dan mengembalikan kemungkinan bahwa gambar tersebut dihasilkan AI.\n\nKelemahannya:\n\n- hasilnya probabilistik,\n- bisa false positive,\n- bisa false negative,\n- akurasi dapat turun pada gambar yang dikompresi berat atau berasal dari generator baru.\n\nJadi strategi yang lebih jujur adalah:\n\n“Inilah yang bisa dibuktikan secara kriptografis, inilah perkiraan model, dan inilah tingkat keyakinan gabungannya.”\n\nJika 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).\n\n## Arsitektur Layanan\n\nLayanan ini hanya memiliki satu endpoint dan dua sinyal downstream.\n\n```\n                ┌─────────────────────────────┐\n   gambar ──▶   │   FastAPI POST /verify       │\n                │                              │\n                │   1. Validasi unggahan       │\n                │   2. Baca manifes C2PA       │\n                │   3. Panggil classifier AI   │\n                │   4. Gabungkan putusan       │\n                └─────────────────────────────┘\n                              │\n                              ▼\n                   JSON verdict + confidence\n```\n\nStack yang digunakan:\n\n- Python 3.10+\n- FastAPI\n- Uvicorn\n`python-multipart`\n\n`httpx`\n\n`c2pa-python`\n\nInstal dependensi:\n\n```\npip install fastapi \"uvicorn[standard]\" python-multipart httpx c2pa-python\n```\n\n## Membaca Sinyal C2PA\n\nPustaka `c2pa-python`\n\nadalah binding Python untuk pustaka Rust `c2pa-rs`\n\n. Kita akan menggunakannya untuk membaca manifes C2PA dari file gambar.\n\nBuat file `provenance.py`\n\n:\n\n``` python\n# provenance.py\nimport json\nimport c2pa\n\ndef read_provenance(image_path: str) -> dict:\n    \"\"\"\n    Baca dan validasi manifes C2PA dari gambar.\n    Mengembalikan dict yang dinormalisasi.\n    \"\"\"\n    try:\n        with c2pa.Reader(image_path) as reader:\n            manifest_store = json.loads(reader.json())\n\n    except c2pa.C2paError as err:\n        if str(err).startswith(\"ManifestNotFound\"):\n            return {\n                \"has_manifest\": False,\n                \"validation\": \"none\",\n                \"detail\": \"Tidak ada manifes C2PA di gambar ini.\",\n            }\n\n        return {\n            \"has_manifest\": True,\n            \"validation\": \"error\",\n            \"detail\": f\"Tidak dapat mengurai manifes: {err}\",\n        }\n\n    active_label = manifest_store.get(\"active_manifest\")\n    manifests = manifest_store.get(\"manifests\", {})\n    active = manifests.get(active_label, {})\n\n    validation_status = manifest_store.get(\"validation_status\", [])\n    validation = \"valid\" if not validation_status else \"invalid\"\n\n    claim_generator = active.get(\"claim_generator\", \"unknown\")\n    signature_issuer = active.get(\"signature_info\", {}).get(\"issuer\", \"unknown\")\n\n    return {\n        \"has_manifest\": True,\n        \"validation\": validation,\n        \"claim_generator\": claim_generator,\n        \"signature_issuer\": signature_issuer,\n        \"validation_status\": validation_status,\n        \"detail\": \"Manifes berhasil dibaca.\",\n    }\n```\n\nHal penting:\n\n-\n`ManifestNotFound`\n\nadalah kondisi normal. - Manifes hilang tidak boleh dianggap error.\n-\n`validation_status`\n\nkosong berarti tanda tangan dan hash valid. -\n`validation_status`\n\nberisi data berarti manifes gagal validasi. -\n`claim_generator`\n\ndapat membantu mengidentifikasi apakah alat pembuatnya kamera, editor, atau generator AI.\n\n## Memanggil Pengklasifikasi Deteksi AI\n\nUntuk 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.\n\nEndpoint Sightengine:\n\n```\nhttps://api.sightengine.com/1.0/check.json\n```\n\nBuat file `classifier.py`\n\n:\n\n``` python\n# classifier.py\nimport httpx\n\nSIGHTENGINE_URL = \"https://api.sightengine.com/1.0/check.json\"\n\nasync def classify_image(\n    image_bytes: bytes,\n    filename: str,\n    api_user: str,\n    api_secret: str,\n    timeout_seconds: float = 8.0,\n) -> dict:\n    \"\"\"\n    Kirim gambar ke detektor AI yang di-host.\n    Mengembalikan skor AI-generated yang dinormalisasi.\n    \"\"\"\n    data = {\n        \"models\": \"genai\",\n        \"api_user\": api_user,\n        \"api_secret\": api_secret,\n    }\n\n    files = {\n        \"media\": (filename, image_bytes)\n    }\n\n    try:\n        async with httpx.AsyncClient(timeout=timeout_seconds) as client:\n            response = await client.post(\n                SIGHTENGINE_URL,\n                data=data,\n                files=files,\n            )\n            response.raise_for_status()\n            payload = response.json()\n\n    except httpx.TimeoutException:\n        return {\n            \"available\": False,\n            \"reason\": \"classifier_timeout\",\n        }\n\n    except httpx.HTTPStatusError as err:\n        return {\n            \"available\": False,\n            \"reason\": f\"classifier_http_{err.response.status_code}\",\n        }\n\n    except httpx.HTTPError as err:\n        return {\n            \"available\": False,\n            \"reason\": f\"classifier_error: {err}\",\n        }\n\n    if payload.get(\"status\") != \"success\":\n        return {\n            \"available\": False,\n            \"reason\": payload.get(\"error\", {}).get(\"message\", \"unknown_error\"),\n        }\n\n    ai_score = payload.get(\"type\", {}).get(\"ai_generated\")\n\n    if ai_score is None:\n        return {\n            \"available\": False,\n            \"reason\": \"missing_score_in_response\",\n        }\n\n    return {\n        \"available\": True,\n        \"ai_score\": float(ai_score),\n    }\n```\n\nPrinsip implementasinya:\n\n- Gunakan timeout eksplisit.\n- Jangan biarkan kegagalan vendor menjatuhkan endpoint.\n- Kembalikan\n`available: false`\n\njika classifier timeout atau error. - Perlakukan skor sebagai probabilitas, bukan fakta.\n\nUntuk 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).\n\n## Mendesain Kontrak `POST /verify`\n\nSebelum menulis route FastAPI, desain kontrak endpoint. Ini membuat frontend bisa mulai integrasi lewat mock server sebelum backend selesai.\n\nDengan [Apidog](https://apidog.com?utm_source=dev.to&utm_medium=wanda&utm_content=n8n-post-automation), Anda bisa:\n\n- mendesain endpoint secara visual,\n- mengimpor/menulis OpenAPI,\n- membuat mock server,\n- menyimpan skenario test endpoint,\n- menjalankan test terhadap backend asli.\n\n### Request\n\nEndpoint menerima `multipart/form-data`\n\ndengan satu field:\n\n```\nimage: file\n```\n\n### Response\n\nContoh response:\n\n```\n{\n  \"verdict\": \"likely_ai\",\n  \"confidence\": 0.86,\n  \"signals\": {\n    \"provenance\": {\n      \"has_manifest\": true,\n      \"validation\": \"valid\",\n      \"claim_generator\": \"SomeImageTool/2.1\",\n      \"signature_issuer\": \"Some Issuing CA\"\n    },\n    \"classifier\": {\n      \"available\": true,\n      \"ai_score\": 0.91\n    }\n  },\n  \"explanation\": \"Manifes C2PA yang valid menyebutkan alat gambar AI, dan pengklasifikasi menilai gambar tersebut kemungkinan dihasilkan AI.\",\n  \"checked_at\": \"2026-05-21T09:30:00Z\"\n}\n```\n\nNilai `verdict`\n\nhanya boleh salah satu dari:\n\n```\nlikely_authentic\nlikely_ai\nuncertain\n```\n\nGunakan `uncertain`\n\nketika sinyal lemah, hilang, atau bertentangan.\n\n### Skema OpenAPI\n\nTambahkan komponen response berikut ke spesifikasi OpenAPI Anda:\n\n```\ncomponents:\n  schemas:\n    VerifyResponse:\n      type: object\n      required:\n        - verdict\n        - confidence\n        - signals\n        - checked_at\n      properties:\n        verdict:\n          type: string\n          enum:\n            - likely_authentic\n            - likely_ai\n            - uncertain\n        confidence:\n          type: number\n          format: float\n          minimum: 0\n          maximum: 1\n        signals:\n          type: object\n          properties:\n            provenance:\n              type: object\n              properties:\n                has_manifest:\n                  type: boolean\n                validation:\n                  type: string\n                  enum:\n                    - valid\n                    - invalid\n                    - error\n                    - none\n                claim_generator:\n                  type: string\n                signature_issuer:\n                  type: string\n            classifier:\n              type: object\n              properties:\n                available:\n                  type: boolean\n                ai_score:\n                  type: number\n                  format: float\n        explanation:\n          type: string\n        checked_at:\n          type: string\n          format: date-time\n```\n\nJika 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).\n\n## Menggabungkan Dua Sinyal\n\nBuat file `verdict.py`\n\n.\n\nLogika berikut konservatif:\n\n- manifes C2PA valid lebih kuat daripada classifier,\n- manifes gagal validasi menghasilkan\n`uncertain`\n\n, - classifier digunakan saat manifes tidak tersedia,\n- konflik sinyal menghasilkan\n`uncertain`\n\n.\n\n``` php\n# verdict.py\n\ndef combine_signals(provenance: dict, classifier: dict) -> dict:\n    \"\"\"\n    Gabungkan sinyal asal-usul dan classifier menjadi satu putusan.\n    \"\"\"\n    has_manifest = provenance.get(\"has_manifest\", False)\n    validation = provenance.get(\"validation\", \"none\")\n    generator = (provenance.get(\"claim_generator\") or \"\").lower()\n\n    classifier_ok = classifier.get(\"available\", False)\n    ai_score = classifier.get(\"ai_score\")\n\n    ai_keywords = (\n        \"firefly\",\n        \"dall-e\",\n        \"dalle\",\n        \"midjourney\",\n        \"stable\",\n        \"gpt\",\n        \"gemini\",\n        \"imagen\",\n        \"generat\",\n    )\n\n    generator_looks_ai = any(keyword in generator for keyword in ai_keywords)\n\n    # 1. Manifes valid menyebut alat AI.\n    if has_manifest and validation == \"valid\" and generator_looks_ai:\n        return _verdict(\n            \"likely_ai\",\n            0.95,\n            \"Manifes C2PA yang valid menyebutkan alat gambar AI.\",\n        )\n\n    # 2. Manifes valid dari alat non-AI.\n    if has_manifest and validation == \"valid\" and not generator_looks_ai:\n        if classifier_ok and ai_score is not None and ai_score > 0.85:\n            return _verdict(\n                \"uncertain\",\n                0.55,\n                \"Manifes terlihat otentik tetapi pengklasifikasi tidak setuju; sinyal bertentangan.\",\n            )\n\n        return _verdict(\n            \"likely_authentic\",\n            0.9,\n            \"Manifes C2PA yang valid dari alat non-AI tersedia.\",\n        )\n\n    # 3. Manifes ada tetapi gagal validasi.\n    if has_manifest and validation in (\"invalid\", \"error\"):\n        return _verdict(\n            \"uncertain\",\n            0.6,\n            \"Gambar membawa manifes C2PA yang gagal divalidasi.\",\n        )\n\n    # 4. Tidak ada manifes, gunakan classifier.\n    if classifier_ok and ai_score is not None:\n        if ai_score >= 0.7:\n            return _verdict(\n                \"likely_ai\",\n                round(ai_score, 2),\n                \"Tidak ada data asal-usul; pengklasifikasi menilai gambar kemungkinan dihasilkan AI.\",\n            )\n\n        if ai_score <= 0.3:\n            return _verdict(\n                \"likely_authentic\",\n                round(1 - ai_score, 2),\n                \"Tidak ada data asal-usul; pengklasifikasi menilai gambar kemungkinan otentik.\",\n            )\n\n        return _verdict(\n            \"uncertain\",\n            0.5,\n            \"Tidak ada data asal-usul dan skor pengklasifikasi tidak meyakinkan.\",\n        )\n\n    # 5. Tidak ada manifes dan classifier tidak tersedia.\n    return _verdict(\n        \"uncertain\",\n        0.0,\n        \"Tidak ada data asal-usul dan pengklasifikasi tidak tersedia.\",\n    )\n\ndef _verdict(verdict: str, confidence: float, explanation: str) -> dict:\n    return {\n        \"verdict\": verdict,\n        \"confidence\": confidence,\n        \"explanation\": explanation,\n    }\n```\n\nAmbang batas `0.7`\n\n, `0.3`\n\n, dan `0.85`\n\nbukan angka universal. Sesuaikan berdasarkan toleransi risiko produk Anda.\n\n## Membuat Aplikasi FastAPI\n\nBuat file `main.py`\n\n:\n\n``` python\n# main.py\nimport os\nimport tempfile\nfrom datetime import datetime, timezone\n\nfrom fastapi import FastAPI, UploadFile, File, HTTPException\nfrom fastapi.responses import JSONResponse\n\nfrom provenance import read_provenance\nfrom classifier import classify_image\nfrom verdict import combine_signals\n\napp = FastAPI(\n    title=\"API Detektor Gambar AI\",\n    version=\"1.0.0\",\n)\n\nALLOWED_TYPES = {\n    \"image/jpeg\",\n    \"image/png\",\n    \"image/webp\",\n}\n\nMAX_BYTES = 12 * 1024 * 1024  # 12 MB\n\nSIGHTENGINE_USER = os.environ.get(\"SIGHTENGINE_API_USER\", \"\")\nSIGHTENGINE_SECRET = os.environ.get(\"SIGHTENGINE_API_SECRET\", \"\")\n\n@app.post(\"/verify\")\nasync def verify(image: UploadFile = File(...)):\n    # 1. Validasi content type.\n    if image.content_type not in ALLOWED_TYPES:\n        raise HTTPException(\n            status_code=415,\n            detail=(\n                f\"Tipe {image.content_type} tidak didukung. \"\n                \"Kirim JPEG, PNG, atau WebP.\"\n            ),\n        )\n\n    # 2. Baca byte gambar.\n    image_bytes = await image.read()\n\n    if len(image_bytes) == 0:\n        raise HTTPException(\n            status_code=400,\n            detail=\"File kosong.\",\n        )\n\n    if len(image_bytes) > MAX_BYTES:\n        raise HTTPException(\n            status_code=413,\n            detail=\"File melebihi batas 12 MB.\",\n        )\n\n    # 3. Baca C2PA.\n    # c2pa.Reader membutuhkan path file, jadi gunakan file sementara.\n    suffix = os.path.splitext(image.filename or \"\")[1] or \".img\"\n\n    with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:\n        tmp.write(image_bytes)\n        tmp_path = tmp.name\n\n    try:\n        provenance = read_provenance(tmp_path)\n    finally:\n        os.unlink(tmp_path)\n\n    # 4. Panggil classifier jika credential tersedia.\n    if SIGHTENGINE_USER and SIGHTENGINE_SECRET:\n        classifier = await classify_image(\n            image_bytes=image_bytes,\n            filename=image.filename or \"upload\",\n            api_user=SIGHTENGINE_USER,\n            api_secret=SIGHTENGINE_SECRET,\n        )\n    else:\n        classifier = {\n            \"available\": False,\n            \"reason\": \"classifier_not_configured\",\n        }\n\n    # 5. Gabungkan sinyal.\n    result = combine_signals(provenance, classifier)\n\n    return JSONResponse(\n        {\n            \"verdict\": result[\"verdict\"],\n            \"confidence\": result[\"confidence\"],\n            \"signals\": {\n                \"provenance\": {\n                    key: provenance.get(key)\n                    for key in (\n                        \"has_manifest\",\n                        \"validation\",\n                        \"claim_generator\",\n                        \"signature_issuer\",\n                    )\n                },\n                \"classifier\": {\n                    \"available\": classifier.get(\"available\", False),\n                    \"ai_score\": classifier.get(\"ai_score\"),\n                },\n            },\n            \"explanation\": result[\"explanation\"],\n            \"checked_at\": datetime.now(timezone.utc).isoformat(),\n        }\n    )\n```\n\nJalankan server lokal:\n\n```\nuvicorn main:app --reload\n```\n\nEndpoint aktif di:\n\n```\nhttp://127.0.0.1:8000/verify\n```\n\nContoh request dengan `curl`\n\n:\n\n```\ncurl -X POST http://127.0.0.1:8000/verify \\\n  -F \"image=@sample.jpg\"\n```\n\nJika Anda menggunakan Sightengine, set environment variable terlebih dahulu:\n\n```\nexport SIGHTENGINE_API_USER=\"your_user\"\nexport SIGHTENGINE_API_SECRET=\"your_secret\"\n```\n\nDesain 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).\n\n## Mocking dan Testing dengan Apidog\n\nFrontend tidak perlu menunggu backend selesai. Setelah kontrak OpenAPI tersedia, Anda bisa membuat mock server di Apidog.\n\n### 1. Buat mock server\n\nDi Apidog:\n\n- Buat project baru.\n- Tambahkan endpoint\n`POST /verify`\n\n. - Set body sebagai\n`multipart/form-data`\n\n. - Tambahkan field\n`image`\n\nbertipe file. - Tambahkan response schema\n`VerifyResponse`\n\n. - Buat contoh response untuk beberapa skenario.\n\nContoh skenario mock yang sebaiknya dibuat:\n\n-\n`likely_authentic`\n\ndengan manifes kamera valid, -\n`likely_ai`\n\ndengan manifes dari alat AI, -\n`uncertain`\n\nketika classifier tidak tersedia, - error\n`415`\n\nuntuk tipe file tidak didukung, - error\n`413`\n\nuntuk file terlalu besar.\n\nFrontend dapat mengarahkan `fetch`\n\nke URL mock Apidog. Saat backend asli siap, ganti base URL saja.\n\n### 2. Jalankan test endpoint\n\nSetelah backend lokal berjalan:\n\n- Buat request\n`POST /verify`\n\ndi Apidog. - Set URL ke\n`http://127.0.0.1:8000/verify`\n\n. - Di Body, pilih\n`form-data`\n\n. - Tambahkan field\n`image`\n\n. - Set tipe field ke\n`File`\n\n. - Pilih gambar uji.\n- Kirim request.\n\nTambahkan assertion:\n\n- status response adalah\n`200`\n\n, -\n`verdict`\n\nada, -\n`verdict`\n\nadalah salah satu dari`likely_authentic`\n\n,`likely_ai`\n\n,`uncertain`\n\n, -\n`confidence`\n\nangka antara`0`\n\ndan`1`\n\n, -\n`signals.provenance.has_manifest`\n\nbertipe boolean, -\n`signals.classifier.available`\n\nbertipe boolean.\n\nBuat test suite kecil dengan beberapa file:\n\n- gambar dengan Kredensial Konten,\n- JPEG biasa tanpa manifes,\n- file terlalu besar,\n- file non-gambar yang diganti ekstensi menjadi\n`.jpg`\n\n, - gambar yang memicu timeout/mock classifier unavailable.\n\nDengan cara ini, perubahan pada fungsi putusan bisa langsung diuji ulang.\n\n## Penguatan Implementasi\n\nEndpoint verifikasi menerima input yang bisa bersifat adversarial. Tambahkan perlindungan berikut sebelum production.\n\n### Validasi file sebenarnya\n\nContent-Type bisa dipalsukan. Untuk validasi lebih kuat, decode gambar dengan Pillow:\n\n```\npip install pillow\n```\n\nContoh validasi:\n\n``` python\nfrom io import BytesIO\nfrom PIL import Image, UnidentifiedImageError\n\ndef validate_image_bytes(image_bytes: bytes) -> None:\n    try:\n        with Image.open(BytesIO(image_bytes)) as img:\n            img.verify()\n    except UnidentifiedImageError:\n        raise ValueError(\"File bukan gambar valid.\")\n```\n\nLalu panggil sebelum membaca C2PA atau classifier.\n\n### Jangan anggap manifes hilang sebagai error\n\nKasus paling umum adalah gambar tanpa manifes. Ini bukan `500`\n\n, bukan bukti palsu, dan bukan bukti asli.\n\nRespons yang benar biasanya tetap `200`\n\ndengan sinyal:\n\n```\n{\n  \"has_manifest\": false,\n  \"validation\": \"none\"\n}\n```\n\n### Tangani timeout classifier\n\nClassifier adalah dependency jaringan. Gunakan timeout pendek dan perlakukan kegagalan sebagai sinyal tidak tersedia.\n\n```\n{\n  \"available\": false,\n  \"reason\": \"classifier_timeout\"\n}\n```\n\nJangan biarkan vendor yang lambat menjatuhkan endpoint Anda.\n\n### Waspadai manifes palsu atau rusak\n\nManifes yang ada belum tentu valid. Selalu cek `validation_status`\n\n.\n\n- kosong: valid,\n- berisi data: gagal validasi.\n\nManifes gagal validasi harus menghasilkan `uncertain`\n\n, bukan `likely_authentic`\n\n.\n\n### Batasi ukuran file\n\nContoh kode memakai batas 12 MB. Untuk production:\n\n- batasi ukuran request di reverse proxy,\n- batasi ukuran di aplikasi,\n- gunakan rate limiting,\n- logging minimal,\n- hindari menyimpan gambar pengguna lebih lama dari yang dibutuhkan.\n\n### Perhatikan privasi\n\nAnda menerima gambar pengguna dan mungkin mengirimkannya ke vendor pihak ketiga. Pastikan:\n\n- tidak mencatat byte gambar,\n- file sementara dihapus,\n- kebijakan privasi menjelaskan pemrosesan pihak ketiga,\n- penggunaan vendor sesuai dengan kebutuhan produk Anda.\n\n## Apa yang Ditangkap dan Dilewatkan Tiap Sinyal\n\n| Skenario | Sinyal asal-usul C2PA | Sinyal pengklasifikasi |\n|---|---|---|\n| Gambar AI dari alat yang menulis Kredensial Konten | Menangkapnya: manifes menyebutkan generator | Biasanya menangkapnya: artefak visual hadir |\n| Gambar AI dengan metadata dihapus | Melewatkannya: tidak ada manifes | Menangkapnya: bekerja pada piksel |\n| Foto asli dari kamera yang menandatangani Kredensial Konten | Memverifikasi: manifes valid dan generator non-AI | Bisa false positive pada kompresi/edit berat |\n| Foto asli tanpa metadata | Tidak ada sinyal | Hanya estimasi probabilistik |\n| Gambar dengan manifes palsu atau dirusak | Menangkapnya lewat `validation_status`\n|\nMungkin menangkap, mungkin tidak |\n| Generator baru yang belum ada di data training | Menangkap hanya jika alat menulis manifes | Bisa melewatkan karena out-of-distribution |\n| Foto asli dengan retouch AI | Jika ada, manifes mencatat riwayat edit | Ambigu; skor bisa berada di tengah |\n\nKesimpulannya: C2PA kuat tetapi tidak selalu ada. Classifier selalu bisa dijalankan pada piksel, tetapi hasilnya tidak pasti. Gabungan keduanya lebih berguna daripada salah satu saja.\n\n## Kasus Penggunaan\n\nPola `POST /verify`\n\nini cocok untuk:\n\n**Platform konten buatan pengguna**\n\nTandai gambar yang kemungkinan AI atau memiliki manifes gagal validasi.**Ruang berita dan fact-checking**\n\nBerikan editor sinyal asal-usul dan skor classifier dalam satu respons.**Asuransi dan klaim**\n\nFlag bukti foto yang terlihat sintetis atau memiliki metadata rusak.**Pipeline aset internal**\n\nCegah gambar AI masuk ke library tanpa label.**CMS sadar provenance**\n\nTampilkan badge terverifikasi ketika Kredensial Konten valid tersedia.\n\n## Kesimpulan\n\nDeteksi gambar AI yang baik bukan tentang menemukan satu tes sempurna. Yang lebih realistis adalah menggabungkan sinyal independen dan menyatakan ketidakpastian secara eksplisit.\n\nRingkasnya:\n\n- C2PA memberi sinyal kriptografis yang kuat, tetapi sering tidak tersedia.\n- Classifier memberi sinyal universal, tetapi probabilistik.\n- FastAPI cukup untuk membangun layanan\n`POST /verify`\n\nkecil dan fokus. - Putusan tiga nilai lebih jujur daripada boolean.\n- OpenAPI + Apidog membantu frontend dan backend bekerja paralel lewat mock server dan test suite.\n\nLangkah berikutnya: desain skema `/verify`\n\n, buat mock server di Apidog, jalankan test endpoint, lalu ganti mock URL ke backend asli saat implementasi siap.", "url": "https://wpnews.pro/news/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi", "canonical_source": "https://dev.to/walse/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi-2cbm", "published_at": "2026-05-21 08:21:22+00:00", "updated_at": "2026-05-21 08:31:34.800618+00:00", "lang": "en", "topics": ["artificial-intelligence", "machine-learning", "developer-tools", "cybersecurity"], "entities": ["C2PA", "FastAPI", "Apidog", "OpenAPI"], "alternates": {"html": "https://wpnews.pro/news/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi", "markdown": "https://wpnews.pro/news/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi.md", "text": "https://wpnews.pro/news/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi.txt", "jsonld": "https://wpnews.pro/news/bangun-api-pendeteksi-gambar-ai-dengan-c2pa-klasifikasi.jsonld"}}