A-EYE

사진 한 장을 보내면 판정을 JSON으로 돌려받습니다

A-EYE 분석 서버를 다른 서비스에서 부르는 방법입니다. A-EYE 앱과 같은 서버, 같은 판정입니다. 키 없이 바로 시험할 수 있습니다. 하루 300회보다 많이 보내거나, 다른 서비스와 IP를 나눠 쓰는 서버(클라우드 함수 등)에서 부른다면 키를 받아 주세요.

서버 https://wonjun12-aeye-backend.hf.space · 2026년 10월 15일 서버 코드 기준

  1. 받기

    요청이 밝힌 크기와 API 키를 확인합니다.

    멈추면 400 401 413

  2. 세기

    키마다, 키가 없으면 IP마다 분당·하루 횟수를 셉니다. 여기서부터 횟수에 들어갑니다.

    멈추면 429

  3. 검사

    image 필드, 파일 형식, 바이트 수, 화소 수를 봅니다.

    멈추면 400 413 415 422

  4. 대기

    한 번에 한 장만 분석합니다. 8건까지 줄을 서서 최대 30초 기다립니다.

    멈추면 503

  5. 분석

    판정과 의심 영역 그림을 돌려줍니다. 분석 중에 실패하면 422입니다.

    끝나면 200

빠른 시작

사진 파일을 multipart/form-data의 image 필드에 담아 보냅니다. 키가 없어도 됩니다.

curl

curl -sS https://wonjun12-aeye-backend.hf.space/api/analyze \
  -F "image=@photo.jpg;filename=image.jpg"

Python (requests)

import time
import requests

BASE = "https://wonjun12-aeye-backend.hf.space"

def analyze(path, api_key=None):
    headers = {"X-API-Key": api_key} if api_key else {}
    for attempt in range(2):
        with open(path, "rb") as f:
            # 파일 이름도 판정에 쓰일 수 있어 중립적인 이름으로 보냅니다
            r = requests.post(f"{BASE}/api/analyze", headers=headers,
                              files={"image": ("image.jpg", f)}, timeout=120)
        wait = int(r.headers.get("Retry-After", "0"))
        if r.status_code in (429, 503) and attempt == 0 and wait <= 60:
            time.sleep(wait)  # 분당 한도나 대기열: 서버가 알려 준 만큼만 쉬고 한 번 더
            continue
        r.raise_for_status()
        return r.json()

result = analyze("photo.jpg")
print(result["display_band"], result["display_score"])

응답에서 판정은 display_band로 읽습니다(판정 읽는 법).

키와 인증

지금은 키 없이도 부를 수 있고, 이때는 요청한 IP마다 횟수를 셉니다. 키를 받으면 그 키로 따로 셉니다. 키는 둘 중 한 헤더로 보냅니다.

X-API-Key: <키>
Authorization: Bearer <키>
  • 등록되지 않은 키를 보내면 401 Invalid API key입니다. 키 없는 요청으로 바꿔서 처리하지 않습니다.
  • 서버가 키를 필수로 요구하도록 설정되어 있으면, 키 없는 요청은 401 API key required (X-API-Key header)를 받습니다.
  • 서버에는 키의 해시만 저장되어 있어 잃어버린 키를 다시 알려 드릴 수 없습니다. 새 키를 받아 주세요.
  • 키를 앱이나 웹 페이지 코드에 넣지 마세요. 서버에서만 보내 주세요.

키 받기

키 요청 창구: oscar43@naver.com. 서비스 이름, 하루 예상 요청 수, 이용자 사진을 보내는지 여부를 적어 보내 주세요.

이용자의 사진을 A-EYE로 보내는 서비스라면, 그 이용자에게 사진이 A-EYE 분석 서버로 전송된다는 사실(처리 위탁과 국외 이전)을 알려야 합니다. A-EYE 쪽 처리는 개인정보처리방침에 있습니다. 키는 이 사실을 이용자에게 알리고 있는지, 평가·연구 목적인지 확인한 뒤 드립니다.

사용량 제한

구분분당하루세는 단위
키 없이20회300회요청한 IP
키로10회1,000회키

키는 같은 IP의 다른 이용자와 횟수를 나누지 않고 하루 1,000회까지 쓸 수 있습니다. 분당을 10회로 둔 것은 한 번에 한 장씩 분석하는 무료 서버를 A-EYE 앱 이용자와 나눠 쓰기 때문입니다.

  • 분당 횟수는 그 창의 첫 요청부터 60초, 하루 횟수는 첫 요청부터 24시간(86,400초) 단위로 셉니다. 자정에 초기화되지 않습니다.
  • POST /api/analyze와 POST /predict는 같은 횟수에서 셉니다. 키 확인을 통과한 요청은 형식 오류(400·413·415·422)나 대기열 초과(503)로 끝나도 한 번으로 셉니다. 받기 단계에서 거절된 요청(401, 그리고 Content-Length 헤더만 보고 거절한 400·413)과 429는 세지 않습니다. GET 주소는 세지 않습니다.
  • 횟수를 센 응답에는 X-RateLimit-Remaining(이번 분에 남은 횟수) 헤더가 붙습니다. 429에는 창이 끝날 때까지 남은 초가 Retry-After로 붙습니다.
  • 분석은 한 번에 한 장씩 합니다. 차례를 기다릴 수 있는 요청은 8건이고, 그 이상이면 바로 503(Retry-After: 2)입니다. 줄에 선 요청도 30초 안에 차례가 오지 않으면 503(Retry-After: 30)입니다.

POST/api/analyze

요청

  • multipart/form-data, 파일 필드 이름은 정확히 image 하나입니다. 이름이 다르면 422입니다.
  • 형식: JPEG, PNG, WebP. 확장자나 Content-Type이 아니라 파일 앞부분 바이트로 판단합니다. HEIC, GIF, 움직이는 PNG·WebP는 415입니다.
  • 크기: 파일 29,360,128바이트(28 MiB)까지. 화소(가로×세로): JPEG 64,000,000, PNG·WebP 32,000,000까지.
  • 파일은 받은 그대로 보내 주세요. 파일 안의 C2PA·메타데이터 기록도 판정에 쓰이고, 다시 저장하거나 압축하면 이 기록과 흔적이 줄어듭니다.
  • 업로드한 파일 이름도 서버가 읽습니다. 생성 도구가 붙이는 이름이나 스크린샷 이름은 결과에 영향을 줄 수 있으니, 원래 이름이 필요 없으면 image.jpg처럼 중립적인 이름으로 보내 주세요.

응답 필드

아래 필드만 연동에 쓰세요. 응답에는 이 표에 없는 필드도 들어 있지만 내부 점검용이라 이름과 값이 예고 없이 바뀌거나 빠질 수 있습니다. 필드가 새로 생길 수 있으니 모르는 필드는 무시하도록 만들어 주세요.

필드값뜻
display_band"real", "uncertain", "ai"최종 판정 세 갈래. 앱이 보여 주는 진짜 사진·판단 유보·AI 의심과 같습니다. 판정은 이 값으로 하세요.
display_score정수, 100점 만점앱이 보여 주는 AI 판별 점수이고, 사람에게 보여 줄 숫자는 이것 하나입니다. score에 100을 곱한 값이 아니라 언제나 display_band와 맞도록 다시 그린 값입니다. 클수록 AI가 만든 이미지 쪽이며, 정답률을 뜻하지 않습니다.
verdict"REAL", "AI"두 갈래 판정입니다. 지금 서버는 이 두 값만 보내고 셋째 값은 없습니다(판단 유보 같은 구분은 display_band로만 옵니다). 판단 유보도 "REAL"로 오므로 이 값만으로는 판단 유보를 알 수 없습니다.
score0과 1 사이 실수파일 속 기록과 서버 보정까지 반영한 점수입니다. 1에 가까울수록 AI 쪽이지만, 판단 유보나 진짜 사진으로 나온 결과에서도 1에 가까울 수 있으니 사람에게 그대로 보여 주지 마세요. 판정 경계는 보장하지 않으며 모델마다 바뀌므로, 이 값에 직접 문턱을 두지 말고 display_band를 쓰세요.
basis문자열판정의 근거입니다. "model"은 이미지 분석, "provenance"는 이미지 안의 C2PA 매니페스트가 AI 생성을 선언한 기록(서명은 검증하지 않음), "provenance_hint"는 서명 없는 기록(메타데이터 표시나 생성 도구 파일 이름)이 진짜 쪽 사진을 판단 유보로 올린 결과, "local_edit"는 부분 편집 의심입니다. 다른 값이 더 올 수 있습니다. 설명에만 쓰세요.
partial_edit_suspecttrue, false진짜 사진의 일부가 편집됐을 수 있다는 신호입니다. true이면 display_band는 "uncertain", basis는 "local_edit"입니다.
heatmap_b64PNG, base64verdict가 "AI"이거나 partial_edit_suspect가 true이면 의심 영역 열지도(긴 변 768px 이하)입니다. 그렇지 않으면(verdict가 "AI"가 아니고 partial_edit_suspect가 false) 빈 문자열입니다. 화면에는 보낸 원본을 쓰세요.
overlay_b64PNG, base64같은 조건에서 원본 위에 의심 영역을 표시한 그림입니다. 그렇지 않으면 원본을 줄인 사진이며, 앞으로 빈 문자열이 될 수 있습니다.
heatmap_style"spotlight", "jet"overlay_b64를 그린 방식입니다. spotlight는 의심 영역 밖을 어둡게 하고 경계선을 그리고, jet은 색 열지도를 겹칩니다.
input_kind"photo", "screen_capture", "unknown"서버가 본 입력 종류입니다. 판정 뒤에 따로 계산하는 참고값이고, 서버에서 이 기능이 꺼져 있으면 필드 자체가 없습니다.
model_version문자열판정한 모델의 이름입니다. 뒤에 붙는 표시를 포함해 형식은 바뀔 수 있습니다. 결과를 저장한다면 함께 저장해 두세요.
elapsed_ms정수서버 안에서 분석에 걸린 밀리초입니다. 줄을 서서 기다린 시간은 들어가지 않습니다.

그림 필드에는 data: 접두어가 없습니다. 화면에 띄우려면 data:image/png;base64,를 앞에 붙이세요.

응답 예시(값은 설명용, 그림은 줄임)

{
  "display_band": "uncertain",
  "display_score": 49,
  "verdict": "REAL",
  "score": 0.91,
  "basis": "model",
  "partial_edit_suspect": false,
  "heatmap_b64": "iVBORw0KGgo…",
  "overlay_b64": "iVBORw0KGgo…",
  "heatmap_style": "spotlight",
  "input_kind": "photo",
  "model_version": "best91(v6)+best91_loc",
  "elapsed_ms": 3140,
  …
}

이 예에서 판정은 판단 유보이고, 사람에게 보여 줄 숫자는 49입니다. verdict는 "REAL", score는 0.91이라 둘 중 하나만 보면 판정을 잘못 읽게 됩니다.

판정 읽는 법

display_band가 판정이고, basis는 그 이유를 덧붙입니다. A-EYE 앱은 이렇게 보여 줍니다.

display_band앱에 보이는 말덧붙일 것
"real"진짜 사진AI로 만든 이미지의 특징이 약하거나 보이지 않았다는 뜻입니다. 진짜라는 증명은 아닙니다.
"uncertain"판단 유보, 또는 basis가 "local_edit"이면 부분 편집 의심어느 쪽이라고 말하기 어렵다는 뜻이며 오류가 아닙니다. 메신저로 여러 번 전달되거나 압축된 진짜 사진에서도 나옵니다. basis가 "provenance_hint"이면 사진 자체는 진짜 쪽이지만 서명 없는 AI 기록(메타데이터 표시나 파일 이름) 때문에 유보한 것입니다.
"ai"AI 의심, 또는 basis가 "provenance"이면 파일 속 C2PA 기록에 따른 결과"provenance"는 이미지 안의 C2PA 매니페스트가 AI 생성을 선언한 경우입니다. 서버는 그 기록의 서명을 검증하지 않습니다. 서명 없는 메타데이터 표시나 파일 이름만으로는 "ai"가 되지 않습니다.

POST/predict

예전 앱이 쓰던 형식을 그대로 돌려주는 호환 주소입니다. 새로 연동한다면 /api/analyze를 쓰세요. 이 응답에는 display_band가 없어서 판단 유보를 구분할 수 없습니다.

  • 요청은 /api/analyze와 같습니다. 같은 image 필드, 같은 크기 제한, 같은 횟수입니다.
  • 응답의 fake_prob은 score에 100을 곱한 실수, real_prob은 100에서 fake_prob을 뺀 값입니다. fake_prob을 사람에게 점수로 보여 주지 마세요. 판단 유보를 모르고, display_score와도 다릅니다. verdict, heatmap_b64, overlay_b64, heatmap_style, model_version, input_kind는 /api/analyze와 같습니다. 그 밖의 필드는 내부 점검용입니다.
  • 오류가 하나 다릅니다. 분석 도중 실패하면 422가 아니라 200과 함께 {"error": "Invalid image"}를 돌려줍니다. 200을 받으면 먼저 error 키가 있는지 보세요.
  • 빈 파일, 깨진 파일, 허용하지 않는 형식, 너무 큰 파일은 /api/analyze와 똑같이 400, 413, 415로 거절됩니다. image 필드가 없을 때도 똑같이 422와 목록 형태의 detail을 받습니다.

GET/api/health

200과 {"status": "ok"}는 서버가 시작을 마치고 모델을 다 불러왔다는 뜻입니다. 서버는 모델을 다 불러온 뒤에야 요청을 받기 시작하므로, 시작하는 동안에는 이 주소도 응답하지 않습니다. 다른 상태값은 없고, 대기열이 찼는지는 알려 주지 않습니다. 횟수에 들어가지 않습니다.

GET/api/version

지금 판정하는 모델의 이름을 model_version으로 돌려줍니다. 함께 오는 다른 필드는 운영 점검용입니다. 횟수에 들어가지 않습니다.

{"model_version": "best91(v6)+best91_loc", …}

오류

오류 본문은 {"detail": "…"}이고 detail은 문자열입니다. 예외는 둘입니다. 요청 형식이 틀렸을 때(FastAPI 검증 오류) 422의 detail은 목록이고, /predict의 분석 실패는 위에서 본 200 응답입니다.

코드detail뜻과 할 일
400Empty upload빈 파일입니다.
400Invalid or corrupt image형식 표시는 맞지만 이미지로 열 수 없습니다. 파일이 깨졌는지 확인하세요.
400Malformed Content-LengthContent-Length 헤더가 숫자가 아닙니다. HTTP 라이브러리가 정하게 두세요.
401Invalid API key등록되지 않은 키입니다.
401API key required (X-API-Key header)서버가 키를 필수로 요구하는데 키가 없습니다.
404Not foundGET으로 부른 없는 주소입니다. GET /api/analyze처럼 분석 주소를 GET으로 불러도 이렇게 나옵니다.
405Method Not Allowed주소가 틀렸거나(끝에 붙은 / 포함) 메서드가 틀렸습니다. 응답의 Allow 헤더가 GET이면 그 주소로는 POST할 수 없다는 뜻이니 주소의 철자와 끝의 /를 확인하고, POST이면 메서드를 POST로 바꾸세요.
413Upload larger than 29360128 bytes파일이 28 MiB를 넘습니다.
413Image has too many pixels (6000x6000); limit for PNG is 32000000화소 수가 한도를 넘습니다(괄호 안은 보낸 크기, 한도는 JPEG 64000000, PNG·WEBP 32000000). 해상도를 줄이세요.
413Image has too many pixels; limit is 32000000파일 머리에 적힌 크기가 너무 커서 열기 전에 거절했습니다(이 경우 보낸 크기는 적히지 않고, 한도는 형식에 따라 다릅니다). 해상도를 줄이세요.
415Only JPEG, PNG and WebP images are accepted허용하지 않는 형식입니다. HEIC 등은 JPEG로 바꿔 보내세요.
415Animated images are not supported움직이는 PNG·WebP입니다.
415Image structure not supported (more than 256 JPEG APPn/COM segments)메타데이터 덩어리가 지나치게 많거나 구조가 비정상인 파일입니다. 괄호 안 이유는 파일마다 다릅니다. 사진을 다시 저장(내보내기)해서 보내세요.
422목록(아래)image 필드가 없습니다. 필드 이름을 확인하세요.
422Image is too small (20x20); the shorter side must be at least 32 px짧은 변이 32px보다 작은 이미지는 분석하지 않습니다.
422Image is too narrow (4000x32); the long side may be at most 100 times the short side긴 변이 짧은 변의 100배를 넘는 이미지는 분석하지 않습니다.
422Image could not be analysed (ref 665a47c3)파일은 통과했지만 분석 중에 실패했습니다. 같은 파일을 다시 보내도 같을 가능성이 높으니 ref 값과 함께 알려 주세요. 위의 크기·비율 422와 달리 ref가 붙습니다.
429Too many requests분당 한도를 넘었습니다. Retry-After초 뒤에 보내세요.
429Daily request quota exceeded하루 한도를 넘었습니다. Retry-After는 그 창이 끝날 때까지의 초입니다.
503Server busy, retry shortly대기열이 찼거나 30초 안에 차례가 오지 않았습니다. Retry-After초 뒤 한 번만 다시 보내세요.
503Model not loaded모델이 준비되지 않았습니다. Retry-After: 10.
500없음(JSON 아님)예상하지 못한 서버 오류입니다. X-Request-Id와 함께 알려 주세요.
502 504없음A-EYE 서버 앞의 호스팅 단계에서 난 오류라 본문이 JSON이 아닐 수 있습니다. 잠시 뒤 한 번 다시 보내세요.

요청 형식이 틀린 422의 본문은 이렇게 생겼습니다. detail이 문자열인지 목록인지로 분석 실패와 구분하세요.

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "image"],
      "msg": "Field required",
      "input": null
    }
  ]
}

A-EYE 서버의 응답에는 대부분 X-Request-Id 헤더(16진수 8자리)가 붙습니다. 분석 실패의 ref 값과 같은 값이니 문의할 때 함께 보내 주세요.

이용 조건

  • 평가·시범용입니다. 가용성과 응답 시간을 보장하지 않고, 서비스 수준 계약(SLA)은 없습니다.
  • 사진을 저장하지 않습니다. 보낸 사진은 결과를 돌려준 직후 폐기되고 모델 학습에 쓰지 않습니다.
  • 결과를 다른 사람에게 보여 줄 때는 추정이라고 밝혀 주세요. 판정을 사실 확인이나 증거처럼 보이게 하지 마세요.
  • 상업 서비스에서 쓰려면 먼저 문의해 주세요. 시범 기간에는 문의를 받은 뒤 개별로 판단합니다.

한계

  • 판정은 추정입니다. 실제 사진을 AI로, AI 이미지를 실제로 판단할 수 있고, 새로 나온 생성 도구의 이미지일수록 놓치기 쉽습니다. 법적 증거, 보도, 인사 결정처럼 중요한 판단의 유일한 근거로 쓰지 말고 출처, 촬영 맥락, 메타데이터를 함께 확인해 주세요.
  • 무료 서버 한 대입니다. CPU 서버 한 대가 한 번에 한 장씩 분석하고, A-EYE 앱 이용자와 같은 서버를 나눠 씁니다. 한꺼번에 많이 보내면 앱 이용자까지 503을 받습니다. 많은 양을 한 번에 처리해야 하면 먼저 문의해 주세요. 가용성은 보장하지 않으며, 서버가 다시 시작하는 동안에는 응답이 없습니다.
  • 화면 캡처는 판단 유보가 자주 나옵니다. 판정 기준이 카메라로 찍은 사진(메신저로 받은 사진 포함)에 맞춰져 있기 때문입니다. 화면 캡처로 보이는 일부 PNG는 서버의 화면 캡처 처리 단계에서 판단 유보가 진짜 사진으로 한 단계 내려갈 수 있고, 이때 score는 사진 기준 점수 그대로입니다. 판정은 display_band를 따르세요. 진짜로 나와도 캡처 속 대화나 금액이 사실이라는 뜻은 아닙니다.
  • 브라우저에서 바로 부를 수 없습니다. 다른 웹사이트의 브라우저 코드에서 부르면 교차 출처(CORS) 설정에 막힙니다. 여러분의 서버에서 호출해 주세요.
  • 사진은 남기지 않습니다. 보낸 사진은 분석하는 동안만 서버 메모리에 있고, 결과를 돌려준 직후 폐기되며 모델 학습에 쓰지 않습니다. 자세한 내용은 개인정보처리방침에 있습니다.

문의

API 문의: oscar43@naver.com. 오류를 알릴 때는 X-Request-Id와 시각을 함께 보내 주세요. 앱 사용 문의는 지원 페이지에 있습니다.