Petnow LogoPetnow
Server API

등록/검증/식별

반려동물 지문 등록, 검증(1:1), 식별(1:N) API를 안내합니다.

개요

Petnow API는 세 가지 핵심 기능을 제공합니다:

기능설명용도
등록 (Registration)펫에 지문 추가새 반려동물 등록
검증 (Verification)1:1 매칭"이 펫이 맞는지" 확인
식별 (Identification)1:N 매칭"이 펫이 누구인지" 찾기

지문 등록 (Add Fingerprints)

캡처 세션의 지문을 펫에 추가합니다.

엔드포인트: POST /v2/pets/{petId}:addFingerprints

전체 플로우

요청

curl -X POST "https://api.petify.petnow.io/v2/pets/pet-uuid-1234:addFingerprints" \
  -H "x-petnow-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session-uuid-abcd"
  }'
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.petify.petnow.io"
HEADERS = {"x-petnow-api-key": API_KEY}

def add_fingerprints(pet_id: str, session_id: str):
    # 지문 추가 요청
    response = requests.post(
        f"{BASE_URL}/v2/pets/{pet_id}:addFingerprints",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"sessionId": session_id}
    )
    job_id = response.json()["data"]["jobId"]
    print(f"Job started: {job_id}")

    # 작업 상태 폴링
    while True:
        status_response = requests.get(
            f"{BASE_URL}/v2/fingerprint-addition-jobs/{job_id}",
            headers=HEADERS
        )
        result = status_response.json()["data"]
        status = result["status"]
        
        print(f"Status: {status}")
        
        if status == "SUCCESS":
            print("Fingerprints added successfully!")
            return result
        elif status == "FAILED":
            raise Exception("Fingerprint addition job failed (status=FAILED)")
        
        time.sleep(2)  # 2초 대기

# 사용 예시
result = add_fingerprints("pet-uuid-1234", "session-uuid-abcd")
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.petify.petnow.io";

async function addFingerprints(petId, sessionId) {
  const headers = { "x-petnow-api-key": API_KEY };

  // 지문 추가 요청
  const response = await fetch(
    `${BASE_URL}/v2/pets/${petId}:addFingerprints`,
    {
      method: "POST",
      headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ sessionId })
    }
  );
  const jobId = (await response.json()).data.jobId;
  console.log(`Job started: ${jobId}`);

  // 작업 상태 폴링
  while (true) {
    const statusResponse = await fetch(
      `${BASE_URL}/v2/fingerprint-addition-jobs/${jobId}`,
      { headers }
    );
    const result = (await statusResponse.json()).data;
    const status = result.status;
    
    console.log(`Status: ${status}`);
    
    if (status === "SUCCESS") {
      console.log("Fingerprints added successfully!");
      return result;
    } else if (status === "FAILED") {
      throw new Error("Fingerprint addition job failed (status=FAILED)");
    }
    
    await new Promise(resolve => setTimeout(resolve, 2000)); // 2초 대기
  }
}

// 사용 예시
const result = await addFingerprints("pet-uuid-1234", "session-uuid-abcd");

응답 (작업 시작)

{
  "success": true,
  "data": {
    "jobId": "job-uuid-1234"
  }
}

작업 상태 조회 응답

{
  "success": true,
  "data": {
    "status": "SUCCESS"
  }
}

지문 추가 작업 상태 응답은 status(PENDING, SUCCESS, FAILED) 하나만 반환합니다 — petId나 addedCount 필드는 없습니다.

펫 검증 (Verification)

캡처된 생체 정보가 특정 펫과 일치하는지 확인합니다 (1:1 매칭).

엔드포인트: POST /v2/pets/{petId}:verify

전체 플로우

요청

curl -X POST "https://api.petify.petnow.io/v2/pets/pet-uuid-1234:verify" \
  -H "x-petnow-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session-uuid-abcd"
  }'
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.petify.petnow.io"
HEADERS = {"x-petnow-api-key": API_KEY}

def verify_pet(pet_id: str, session_id: str):
    # 검증 요청
    response = requests.post(
        f"{BASE_URL}/v2/pets/{pet_id}:verify",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"sessionId": session_id}
    )
    job_id = response.json()["data"]["jobId"]
    print(f"Verification job started: {job_id}")

    # 작업 상태 폴링
    while True:
        status_response = requests.get(
            f"{BASE_URL}/v2/verification-jobs/{job_id}",
            headers=HEADERS
        )
        result = status_response.json()["data"]
        status = result["status"]
        
        if status == "SUCCESS":
            is_verified = result["isVerified"]
            score = result["score"]
            print(f"Verified: {is_verified}, Score: {score}")
            return result
        elif status == "FAILED":
            raise Exception("Verification job failed (status=FAILED)")
        
        time.sleep(2)

# 사용 예시
result = verify_pet("pet-uuid-1234", "session-uuid-abcd")
if result["isVerified"]:
    print("✅ 본인 확인 완료!")
else:
    print("❌ 일치하지 않음")
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.petify.petnow.io";

async function verifyPet(petId, sessionId) {
  const headers = { "x-petnow-api-key": API_KEY };

  // 검증 요청
  const response = await fetch(
    `${BASE_URL}/v2/pets/${petId}:verify`,
    {
      method: "POST",
      headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ sessionId })
    }
  );
  const jobId = (await response.json()).data.jobId;
  console.log(`Verification job started: ${jobId}`);

  // 작업 상태 폴링
  while (true) {
    const statusResponse = await fetch(
      `${BASE_URL}/v2/verification-jobs/${jobId}`,
      { headers }
    );
    const result = (await statusResponse.json()).data;
    
    if (result.status === "SUCCESS") {
      console.log(`Verified: ${result.isVerified}, Score: ${result.score}`);
      return result;
    } else if (result.status === "FAILED") {
      throw new Error("Verification job failed (status=FAILED)");
    }
    
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}

// 사용 예시
const result = await verifyPet("pet-uuid-1234", "session-uuid-abcd");
if (result.isVerified) {
  console.log("✅ 본인 확인 완료!");
} else {
  console.log("❌ 일치하지 않음");
}

검증 결과 응답

{
  "success": true,
  "data": {
    "status": "SUCCESS",
    "isVerified": true,
    "score": 95
  }
}
필드타입설명
statusstringPENDING, SUCCESS, FAILED
isVerifiedboolean일치 여부
scorenumber신뢰도 점수 (0-100)

펫 식별 (Identification)

데이터베이스에서 캡처된 생체 정보와 일치하는 펫을 찾습니다 (1:N 매칭).

검색 풀(search pool): pets:identify 엔드포인트는 항상 default 검색 풀을 대상으로 매칭하며, 이 풀에는 귀하가 등록한 전체 펫이 포함됩니다. 식별 요청은 풀 파라미터를 받지 않습니다. 펫을 여러 검색 풀로 분리하는 것은 Petnow 담당자가 구성하는 고급 기능이므로, 필요하면 문의해 주세요.

엔드포인트: POST /v2/pets:identify

식별은 이를 포함하는 플랜이 필요합니다. 권한이 없는 계정은 이 엔드포인트에서 HTTP 402와 함께 PETNOWB2B10011(플랜에 식별 미포함) 또는 PETNOWB2B10012(플랜 미선택)를 반환합니다. 에러 코드를 참고하세요.

전체 플로우

요청

curl -X POST "https://api.petify.petnow.io/v2/pets:identify" \
  -H "x-petnow-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session-uuid-abcd"
  }'
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.petify.petnow.io"
HEADERS = {"x-petnow-api-key": API_KEY}

def identify_pet(session_id: str):
    # 식별 요청
    response = requests.post(
        f"{BASE_URL}/v2/pets:identify",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"sessionId": session_id}
    )
    job_id = response.json()["data"]["jobId"]
    print(f"Identification job started: {job_id}")

    # 작업 상태 폴링
    while True:
        status_response = requests.get(
            f"{BASE_URL}/v2/identification-jobs/{job_id}",
            headers=HEADERS
        )
        result = status_response.json()["data"]
        status = result["status"]
        
        if status == "SUCCESS":
            pets = result.get("pets", [])
            print(f"Found {len(pets)} matching pet(s)")
            for pet in pets:
                print(f"  - {pet['id']}: score={pet['score']}")
            return result
        elif status == "FAILED":
            raise Exception("Identification job failed (status=FAILED)")
        
        time.sleep(2)

# 사용 예시
result = identify_pet("session-uuid-abcd")
if result["pets"]:
    best_match = result["pets"][0]
    print(f"Best match: {best_match['id']} (score: {best_match['score']})")
else:
    print("No matching pets found")
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.petify.petnow.io";

async function identifyPet(sessionId) {
  const headers = { "x-petnow-api-key": API_KEY };

  // 식별 요청
  const response = await fetch(
    `${BASE_URL}/v2/pets:identify`,
    {
      method: "POST",
      headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ sessionId })
    }
  );
  const jobId = (await response.json()).data.jobId;
  console.log(`Identification job started: ${jobId}`);

  // 작업 상태 폴링
  while (true) {
    const statusResponse = await fetch(
      `${BASE_URL}/v2/identification-jobs/${jobId}`,
      { headers }
    );
    const result = (await statusResponse.json()).data;
    
    if (result.status === "SUCCESS") {
      const pets = result.pets || [];
      console.log(`Found ${pets.length} matching pet(s)`);
      pets.forEach(pet => {
        console.log(`  - ${pet.id}: score=${pet.score}`);
      });
      return result;
    } else if (result.status === "FAILED") {
      throw new Error("Identification job failed (status=FAILED)");
    }
    
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}

// 사용 예시
const result = await identifyPet("session-uuid-abcd");
if (result.pets?.length > 0) {
  const bestMatch = result.pets[0];
  console.log(`Best match: ${bestMatch.id} (score: ${bestMatch.score})`);
} else {
  console.log("No matching pets found");
}

식별 결과 응답

{
  "success": true,
  "data": {
    "status": "SUCCESS",
    "pets": [
      {
        "id": "pet-uuid-1234",
        "score": 95,
        "metadata": "{\"name\": \"버디\", \"owner\": \"홍길동\"}"
      },
      {
        "id": "pet-uuid-5678",
        "score": 78,
        "metadata": "{\"name\": \"맥스\", \"owner\": \"김영희\"}"
      }
    ]
  }
}
필드타입설명
petsarray매칭된 펫 목록 (점수 내림차순)
pets[].idstring펫 ID
pets[].scorenumber신뢰도 점수 (0-100)
pets[].metadatastring펫 메타데이터 (JSON 문자열)

작업 상태 값

모든 비동기 작업은 다음 상태를 가집니다:

상태설명
PENDING처리 중
SUCCESS성공
FAILED실패

결과 필드는 SUCCESS일 때만 포함됩니다. 검증은 isVerified/score, 식별은 pets[]가 SUCCESS 응답에만 들어갑니다. 지문 추가(등록) 작업은 status만 반환하며 추가 결과 필드가 없습니다.
FAILED 응답에는 status만 있고 별도의 사유(error) 필드는 없습니다. 실패 원인이 잘못된 요청 때문이면 작업 시작 시점에 에러 응답(에러 코드)으로 반환되고, 작업이 비동기로 FAILED가 되는 경우(예: 품질 미달)는 재촬영 후 다시 시도하세요.

권장 폴링 설정

항목권장값
폴링 간격2-3초
최대 대기 시간60초
최대 재시도 횟수20-30회

팁: 지수 백오프(exponential backoff)를 사용하면 서버 부하를 줄이고 효율적으로 폴링할 수 있습니다. 초기 간격 1초에서 시작하여 최대 5초까지 증가시키는 방식을 권장합니다.

다음 단계

On this page