Registration/Verification/Identification
Pet fingerprint registration, verification (1:1), and identification (1:N) API guide.
Overview
Petnow API provides three core features:
| Feature | Description | Use Case |
|---|---|---|
| Registration | Add fingerprints to pet | Register new pet |
| Verification | 1:1 matching | Confirm "Is this the right pet?" |
| Identification | 1:N matching | Find "Who is this pet?" |
Add Fingerprints (Registration)
Add fingerprints from a capture session to a pet.
Endpoint: POST /v2/pets/{petId}:addFingerprints
Complete Flow
Request
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):
# Request fingerprint addition
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}")
# Poll for job status
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) # Wait 2 seconds
# Usage example
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 };
// Request fingerprint addition
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}`);
// Poll for job status
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)); // Wait 2 seconds
}
}
// Usage example
const result = await addFingerprints("pet-uuid-1234", "session-uuid-abcd");Response (Job Started)
{
"success": true,
"data": {
"jobId": "job-uuid-1234"
}
}Job Status Response
{
"success": true,
"data": {
"status": "SUCCESS"
}
}The fingerprint-addition job status returns only status (PENDING, SUCCESS, or FAILED) — there are no petId or addedCount fields.
Pet Verification
Verify if captured biometric data matches a specific pet (1:1 matching).
Endpoint: POST /v2/pets/{petId}:verify
Complete Flow
Request
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):
# Request verification
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}")
# Poll for job status
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)
# Usage example
result = verify_pet("pet-uuid-1234", "session-uuid-abcd")
if result["isVerified"]:
print("✅ Identity confirmed!")
else:
print("❌ Does not match")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 };
// Request verification
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}`);
// Poll for job status
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));
}
}
// Usage example
const result = await verifyPet("pet-uuid-1234", "session-uuid-abcd");
if (result.isVerified) {
console.log("✅ Identity confirmed!");
} else {
console.log("❌ Does not match");
}Verification Result Response
{
"success": true,
"data": {
"status": "SUCCESS",
"isVerified": true,
"score": 95
}
}| Field | Type | Description |
|---|---|---|
status | string | PENDING, SUCCESS, FAILED |
isVerified | boolean | Whether verification passed |
score | number | Confidence score (0-100) |
Pet Identification
Find matching pets in the database from captured biometric data (1:N matching).
Search pool: the pets:identify endpoint always matches against the default search pool, which contains all of your registered pets. The identify request does not take a pool parameter. Segmenting pets into separate search pools is an advanced feature configured by your Petnow representative — contact us if you need it.
Endpoint: POST /v2/pets:identify
Identification requires a plan that includes it. If your account is not entitled, this endpoint returns HTTP 402 with PETNOWB2B10011 (identification not in plan) or PETNOWB2B10012 (no plan selected yet). See Error Codes.
Complete Flow
Request
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):
# Request identification
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}")
# Poll for job status
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)
# Usage example
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 };
// Request identification
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}`);
// Poll for job status
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));
}
}
// Usage example
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");
}Identification Result Response
{
"success": true,
"data": {
"status": "SUCCESS",
"pets": [
{
"id": "pet-uuid-1234",
"score": 95,
"metadata": "{\"name\": \"Buddy\", \"owner\": \"John Doe\"}"
},
{
"id": "pet-uuid-5678",
"score": 78,
"metadata": "{\"name\": \"Max\", \"owner\": \"Jane Smith\"}"
}
]
}
}| Field | Type | Description |
|---|---|---|
pets | array | Matched pets list (sorted by score descending) |
pets[].id | string | Pet ID |
pets[].score | number | Confidence score (0-100) |
pets[].metadata | string | Pet metadata (JSON string) |
Job Status Values
All async jobs have the following statuses:
| Status | Description |
|---|---|
PENDING | Processing |
SUCCESS | Completed successfully |
FAILED | Failed |
Result fields are present only on SUCCESS. isVerified/score (verification) and pets[] (identification) appear only in the SUCCESS response. The fingerprint-addition (registration) job returns only status with no additional result fields.
A FAILED response contains only status — there is no separate error/reason field. If the failure is due to a bad request, it is returned as an error response at job-submission time (see Error Codes); if a job fails asynchronously (e.g. insufficient image quality), capture again and retry.
Recommended Polling Settings
| Item | Recommended Value |
|---|---|
| Polling Interval | 2-3 seconds |
| Max Wait Time | 60 seconds |
| Max Retry Count | 20-30 times |
Tip: Using exponential backoff can reduce server load and make polling more efficient. We recommend starting at 1 second and increasing up to 5 seconds maximum.