Server Integration
고객사 서버에서 촬영 세션을 발급하고, 신뢰 가능한 결과를 수신하는 Server Integration API를 안내합니다.
개요
Server Integration은 고객사 서버가 API로 1회용 촬영 세션을 사용자마다 발급하고, 완료 결과를 신뢰 가능한 형태로 서버에서 수신하는 방식입니다. Server API와 동일한 호스트·인증(x-petnow-api-key)을 사용합니다.
| 항목 | 값 |
|---|---|
| Base URL | https://api.petify.petnow.io |
| 인증 | x-petnow-api-key 헤더 (API Key 발급) |
| 응답 형식 | 성공: { "success": true, "data": ... } / 실패: { "errors": [{ "code": "PETNOWB2B..." }] } |
전체 라운드트립
- 서버가
POST /v2/hosted-sessions로 세션을 발급받고, 응답의 1회용 웹 URL을 사용자에게 전달합니다. - 사용자가 Petnow 촬영 페이지에서 촬영을 마치면 브라우저가
redirectUrl로 복귀합니다. 완료 시petify_code에 1회용 결과 코드가 담깁니다. - 서버가
GET /v2/hosted-results/{code}로 코드를 1회 소비하여 신뢰 가능한 결과를 받습니다. - 리다이렉트나 코드를 유실했다면 언제든
GET /v2/hosted-sessions/{id}로 같은 결과를 조회할 수 있습니다.
반려동물 식별 모델
이 API에서 반려동물은 고객사 시스템의 ID(externalPetId)로만 참조됩니다. Petify 내부 ID는 노출되지 않습니다. 등록(REGISTER) 시 externalPetId가 반려동물과 연결(매핑)되고, 이후 인증·검색 결과도 이 ID로 반환됩니다.
매핑은 Web Integration의 등록을 통해서만 생성됩니다. 기존 모바일 SDK나 Server API(/v2/pets)로 등록한 반려동물에는 매핑이 없으므로 인증 대상으로 지정할 수 없고(404 PETNOWB2B21002), 검색에서도 후보 ID로 반환되지 않고 unmappedCandidates 개수로만 집계됩니다. 기존 반려동물에 매핑을 추가하는 기능은 제공되지 않습니다.
사전 준비
- Petify Console에서 API Key를 발급합니다. (콘솔 Integration → API & SDK 탭에서도 관리할 수 있습니다.)
- 콘솔의 Integration → Links → 허용 Redirect URL에서 사용할 Redirect URL을 미리 등록합니다.
Server Integration은 허용 목록에 사전 등록된 Redirect URL만 사용할 수 있습니다(정확 일치). 미등록 URL로 세션을 발급하면 400 에러(PETNOWB2B21003)가 반환됩니다. 등록 규칙은 Redirect URL과 결과 전달을 참고하세요.
세션 발급
엔드포인트: POST /v2/hosted-sessions
요청
curl -X POST "https://api.petify.petnow.io/v2/hosted-sessions" \
-H "x-petnow-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "REGISTER",
"species": "DOG",
"redirectUrl": "https://your-service.example.com/petify/return",
"externalPetId": "YOUR-PET-ID-123",
"expiresInSeconds": 1800,
"locale": "ko"
}'import requests
response = requests.post(
"https://api.petify.petnow.io/v2/hosted-sessions",
headers={
"x-petnow-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"action": "REGISTER",
"species": "DOG",
"redirectUrl": "https://your-service.example.com/petify/return",
"externalPetId": "YOUR-PET-ID-123",
"expiresInSeconds": 1800,
"locale": "ko",
},
)
session = response.json()["data"]
# session["id"] → 폴링 핸들 (반드시 저장)
# session["url"] → 사용자에게 전달할 1회용 촬영 URLconst response = await fetch("https://api.petify.petnow.io/v2/hosted-sessions", {
method: "POST",
headers: {
"x-petnow-api-key": "YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "REGISTER",
species: "DOG",
redirectUrl: "https://your-service.example.com/petify/return",
externalPetId: "YOUR-PET-ID-123",
expiresInSeconds: 1800,
locale: "ko"
})
});
const { data: session } = await response.json();
// session.id → 폴링 핸들 (반드시 저장)
// session.url → 사용자에게 전달할 1회용 촬영 URL요청 파라미터
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
action | string | ✅ | REGISTER(등록) / VERIFY(인증) / IDENTIFY(검색) |
species | string | ✅ | DOG 또는 CAT. VERIFY는 대상 반려동물의 종과 일치해야 하고, IDENTIFY는 이 종으로 등록된 반려동물 중에서만 후보를 찾습니다 |
redirectUrl | string | ✅ | 완료 후 복귀할 URL. 허용 목록과 정확히 일치해야 합니다 |
externalPetId | string | 조건부 | 고객사 시스템의 반려동물 ID (최대 255자). REGISTER/VERIFY는 필수, IDENTIFY는 보내면 안 됩니다 |
petMetadata | string | ❌ | REGISTER 전용. 등록되는 반려동물의 metadata 필드에 저장할 자유 형식 문자열 |
expiresInSeconds | integer | ❌ | 세션 만료 시간 (300–86400초, 기본 1800) |
locale | string | ❌ | 촬영 페이지 언어 힌트 (ko 기본, en 지원) |
응답 (201)
{
"success": true,
"data": {
"id": "8f6a1f9e-4c2d-4b7a-9e1a-2f3b4c5d6e7f",
"url": "https://capture.petify.petnow.io/s/hs_QzR4c2VjcmV0dG9rZW4",
"action": "REGISTER",
"species": "DOG",
"integrationMethod": "SERVER_INTEGRATION",
"bindingSource": "SERVER_ISSUED",
"externalPetId": "YOUR-PET-ID-123",
"expiresAt": "2026-08-26T05:30:00Z",
"createdAt": "2026-08-26T05:00:00Z"
}
}id는 반드시 저장하세요 — 발급 시점에 자체 사용자·주문 정보와 함께 저장해 두면 결과의sessionId·externalPetId를 그 값과 대응시켜 어떤 사용자의 건인지 확정할 수 있고, 리다이렉트가 유실됐을 때 결과를 조회할 유일한 수단이기도 합니다.url은 발급 응답에서 한 번만 제공되며 다시 조회할 수 없습니다. 1회용으로 취급하세요 — 같은 URL을 여러 채널로 재전송하는 설계는 피해야 합니다.
결과 수신
촬영이 끝나면 사용자 브라우저가 redirectUrl로 이동하며 petify_status(및 조건부 petify_code)가 쿼리 파라미터로 추가됩니다. 파라미터 규칙 전체는 Redirect URL과 결과 전달을 참고하세요.
결과 코드 소비
petify_status=completed로 받은 1회용 결과 코드를 서버에서 교환합니다.
엔드포인트: GET /v2/hosted-results/{code}
curl -X GET "https://api.petify.petnow.io/v2/hosted-results/hrc_1a2b3c4d5e6f" \
-H "x-petnow-api-key: YOUR_API_KEY"import requests
response = requests.get(
"https://api.petify.petnow.io/v2/hosted-results/hrc_1a2b3c4d5e6f",
headers={"x-petnow-api-key": "YOUR_API_KEY"},
)
result = response.json()["data"]const response = await fetch(
"https://api.petify.petnow.io/v2/hosted-results/hrc_1a2b3c4d5e6f",
{ headers: { "x-petnow-api-key": "YOUR_API_KEY" } }
);
const { data: result } = await response.json();응답 예시 (인증):
{
"success": true,
"data": {
"sessionId": "8f6a1f9e-4c2d-4b7a-9e1a-2f3b4c5d6e7f",
"action": "VERIFY",
"externalPetId": "YOUR-PET-ID-123",
"bindingTrusted": true,
"outcome": "MATCH",
"completedAt": "2026-08-26T05:10:00Z",
"alreadyConsumed": false,
"result": {
"isVerified": true,
"score": 93
}
}
}기능별 result 스키마:
| 기능 | 필드 | 설명 |
|---|---|---|
REGISTER | registered (boolean) | 등록 성공 여부 |
VERIFY | isVerified (boolean), score (0–100) | 일치 여부와 점수 |
IDENTIFY | candidates (배열), unmappedCandidates (integer) | 후보 목록 [{ externalPetId, score }] (점수 내림차순). externalPetId 매핑이 없는 후보는 목록에서 제외되고 unmappedCandidates에 개수로 집계됩니다 |
소비 규칙:
- 결과 코드 조회는 1회 소비(one-shot consume) 입니다. 코드는 세션 완료 후 약 10분간 유효합니다.
- 소비에 성공했지만 응답을 유실한 경우(타임아웃, 장애), 같은 API Key로 5분(기본) 안에 다시 조회하면
alreadyConsumed: true와 함께 동일한 결과가 반환됩니다. 그 이후에는 410이 반환됩니다. - 코드 자체를 유실했다면 아래의 세션 폴링으로 대체하세요.
세션 상태 폴링
리다이렉트·코드 유실 시의 복구 경로이자, 세션 진행 상태를 확인하는 수단입니다. 소비형이 아니므로 여러 번 호출해도 됩니다.
엔드포인트: GET /v2/hosted-sessions/{id}
{
"success": true,
"data": {
"id": "8f6a1f9e-4c2d-4b7a-9e1a-2f3b4c5d6e7f",
"linkId": null,
"action": "VERIFY",
"species": "DOG",
"integrationMethod": "SERVER_INTEGRATION",
"bindingSource": "SERVER_ISSUED",
"externalPetId": "YOUR-PET-ID-123",
"bindingTrusted": true,
"status": "COMPLETED",
"outcome": "MATCH",
"errorCode": null,
"result": { "isVerified": true, "score": 93 },
"expiresAt": "2026-08-26T05:30:00Z",
"completedAt": "2026-08-26T05:10:00Z",
"createdAt": "2026-08-26T05:00:00Z"
}
}세션 상태 (status):
| 상태 | 설명 |
|---|---|
CREATED | 세션이 발급되었고 아직 열리지 않음 |
OPENED | 사용자가 촬영 페이지를 열었음 |
PROCESSING | 제출 후 처리 중 |
COMPLETED | 정상 완료 — outcome과 result가 채워짐 |
FAILED | 실패 — errorCode에 사유가 담김 (예: CAPTURE_FAILED) |
EXPIRED | 완료되지 않고 만료됨 |
CANCELLED | 사용자가 촬영을 취소함 |
outcome은COMPLETED일 때만 채워집니다: 등록SUCCEEDED, 인증·검색MATCH/NO_MATCH.- 이 엔드포인트는 Link Integration 링크로 생성된 세션도 조회할 수 있습니다 (링크 세션은
linkId가 채워지고, 사용자 전달externalPetId는bindingTrusted: false).
에러 코드
에러 응답은 { "errors": [{ "code": "..." }] } 형식입니다. PETNOWB2B10001(요청 검증 실패)은 details 필드로 필드별 오류를 함께 반환합니다.
| 코드 | HTTP | 의미 |
|---|---|---|
PETNOWB2B10000 | 401 | API Key 누락 또는 잘못됨 |
PETNOWB2B10001 | 400 | 요청 검증 실패 (details에 필드별 사유) |
PETNOWB2B10005 | 402 | 등록 가능한 반려동물 수 한도 도달 |
PETNOWB2B10010 | 403 | 결제 문제로 계정이 정지됨 |
PETNOWB2B10011 | 402 | 현재 플랜에 검색(IDENTIFY) 기능이 포함되지 않음 |
PETNOWB2B10012 | 402 | 플랜 선택이 필요함 (콘솔에서 플랜 선택 후 사용 가능) |
PETNOWB2B20015 | 400 | VERIFY 대상 반려동물과 species 불일치 |
PETNOWB2B21001 | 409 | REGISTER: 이미 등록에 사용된 externalPetId |
PETNOWB2B21002 | 404 | VERIFY: 등록 이력이 없는 externalPetId |
PETNOWB2B21003 | 400 | redirectUrl이 허용 목록에 없음 |
PETNOWB2B21004 | 404 | 세션을 찾을 수 없음 |
PETNOWB2B21005 | 404 | 결과 코드가 없거나 만료됨 (Link Integration 표시용 코드도 이 경로에서는 404) |
PETNOWB2B21006 | 410 | 이미 소비된 결과 코드 (유예 시간 경과) |
PETNOWB2B21007 | 503 | 세션 발급이 일시적으로 중단됨 — 짧은 간격의 재시도 없이 백오프 후 재시도하세요 |
다음 단계
- Redirect URL과 결과 전달 - 허용 목록 관리와 리다이렉트 파라미터
- Link Integration - 콘솔에서 링크 발급·관리