API-Methoden
Partner-API-Dokumentation
Diese Referenz beschreibt, wie Ihre Integration im Namen Ihrer Organisation mit Animal-ID kommuniziert: Authentifizierung, erforderliche Header und die zentralen MVP-Endpunkte (Halter, Tiere, Behandlungen, Fotos, Wörterbücher).
SDKs & Client-Bibliotheken
Offizielle Open-Source-SDKs, die die Request-Signierung und die Endpunkte unten kapseln — einfach einbinden, statt Requests manuell zu signieren.
PHP · Composer
Serverseitiges PHP-SDK: HMAC-Request-Signierung und typisierte Clients für jeden Endpunkt.
composer require animal-id/aid-partner-sdk
JavaScript · npm
Framework-Pakete für Node und den Browser, auf einem gemeinsamen Kern aufgebaut.
npm i @animal-id/partner-core
- @animal-id/partner-coreFramework-agnostic core: signing + typed client.
- @animal-id/partner-reactReact hooks & helpers.
- @animal-id/partner-vueVue composables.
- @animal-id/partner-angularAngular services.
- @animal-id/partner-nestjsNestJS module for server-side integrations.
Authentifizierung & Signatur
Base URL: https://gw.animal-id.net · all paths are prefixed with /v1/partner.
Every signed request carries four headers. The signature is an HMAC-SHA256 (hex) of a canonical string, keyed with your private key:
stringToSign = METHOD + "\n" + path[?query] + "\n" + sha256_hex(rawBody) + "\n" + timestamp signature = hex( hmac_sha256(stringToSign, privateKey) )
| Header | Value |
|---|---|
X-Eternity-App-Id | Your application id. |
X-Eternity-Public-Key | Your public key. |
X-Eternity-Timestamp | Unix seconds; must be within ±300s of server time. |
X-Eternity-Signature | The HMAC-SHA256 hex computed above. |
X-Eternity-Idempotency-Key | UUID, required on every POST/PATCH/DELETE. Replays return the first response; same key + different body → 409. |
X-Eternity-Animal-ID-Version | Optional date version (YYYY-MM-DD). Defaults to the version locked when your app was issued. |
Sign and send the exact same body bytes. For GET/DELETE the body is empty
(its sha256 is the hash of an empty string). path includes the query string when present.
For multipart/form-data uploads (photos) the raw body is not part of the
signature either — sign with the sha256 of an empty body.
Every successful response wraps results in a payload array.
Single-resource create/get endpoints return a one-element array
(e.g. { "payload": [ { … } ] }); the per-endpoint examples below show the single object for brevity.
APP_ID="aid_app_xxx"; PUBLIC_KEY="pk_xxx"; PRIVATE_KEY="sk_xxx"
METHOD="POST"
PATH_Q="/v1/partner/owners" # path (+ "?query" if any), exactly as sent
BODY='{"email":"jane@example.com","consent":{"account_creation":true}}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 | awk '{print $2}')
STRING_TO_SIGN=$(printf '%s\n%s\n%s\n%s' "$METHOD" "$PATH_Q" "$BODY_HASH" "$TS")
SIG=$(printf '%s' "$STRING_TO_SIGN" | openssl dgst -sha256 -hmac "$PRIVATE_KEY" | awk '{print $2}')
curl -X "$METHOD" "https://gw.animal-id.net$PATH_Q" \
-H "X-Eternity-App-Id: $APP_ID" \
-H "X-Eternity-Public-Key: $PUBLIC_KEY" \
-H "X-Eternity-Timestamp: $TS" \
-H "X-Eternity-Signature: $SIG" \
-H "X-Eternity-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "$BODY"const crypto = require('crypto');
function sign({ method, pathQ, body = '', privateKey }) {
const ts = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const stringToSign = [method, pathQ, bodyHash, ts].join('\n');
const signature = crypto.createHmac('sha256', privateKey).update(stringToSign).digest('hex');
return { ts, signature };
}
const body = JSON.stringify({ email: 'jane@example.com' });
const { ts, signature } = sign({ method: 'POST', pathQ: '/v1/partner/owners', body, privateKey: 'sk_xxx' });
await fetch('https://gw.animal-id.net/v1/partner/owners', {
method: 'POST',
headers: {
'X-Eternity-App-Id': 'aid_app_xxx',
'X-Eternity-Public-Key': 'pk_xxx',
'X-Eternity-Timestamp': ts,
'X-Eternity-Signature': signature,
'X-Eternity-Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body, // sign and send the SAME bytes
});import hashlib, hmac, time, json, uuid, requests
def sign(method, path_q, body, private_key):
ts = str(int(time.time()))
body_hash = hashlib.sha256(body.encode()).hexdigest()
string_to_sign = "\n".join([method, path_q, body_hash, ts])
signature = hmac.new(private_key.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()
return ts, signature
body = json.dumps({"email": "jane@example.com"}, separators=(",", ":"))
ts, signature = sign("POST", "/v1/partner/owners", body, "sk_xxx")
requests.post(
"https://gw.animal-id.net/v1/partner/owners",
data=body, # send the SAME bytes you signed
headers={
"X-Eternity-App-Id": "aid_app_xxx",
"X-Eternity-Public-Key": "pk_xxx",
"X-Eternity-Timestamp": ts,
"X-Eternity-Signature": signature,
"X-Eternity-Idempotency-Key": str(uuid.uuid4()),
"Content-Type": "application/json",
},
)<?php
function signRequest(string $method, string $pathQ, string $body, string $privateKey): array {
$ts = (string) time();
$bodyHash = hash('sha256', $body);
$stringToSign = implode("\n", [$method, $pathQ, $bodyHash, $ts]);
$signature = hash_hmac('sha256', $stringToSign, $privateKey);
return [$ts, $signature];
}
$body = json_encode(['email' => 'jane@example.com']);
[$ts, $signature] = signRequest('POST', '/v1/partner/owners', $body, 'sk_xxx');
$ch = curl_init('https://gw.animal-id.net/v1/partner/owners');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body, // send the SAME bytes you signed
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Eternity-App-Id: aid_app_xxx",
"X-Eternity-Public-Key: pk_xxx",
"X-Eternity-Timestamp: $ts",
"X-Eternity-Signature: $signature",
'X-Eternity-Idempotency-Key: ' . bin2hex(random_bytes(16)),
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);API-Methoden
Tippen Sie — Abschnitte werden nach Stichwort gefiltert.
/v1/partner/dictionariesWörterbücher (mehrsprachig, filterbar).▸Auth: Public (no signature required). CDN-cacheable via ETag.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
include | no | string (csv) | — | Comma-separated dictionary keys to return; empty → all. Keys: species, sex, sizes, lost_statuses, other_identifiers, procedure_types, countries, languages, cites. |
q | no | string | — | Filter entries by localized name in any active language. |
lang | no | string | languages | Project names to a single locale (uk, en, ru, de, es). Default: all active. |
Example response
{
"payload": [
{
"key": "species",
"items": [
{ "code": 3, "names": { "uk": "Собаки", "en": "Dogs", "ru": "Собаки", "de": "Dogs" } },
{ "code": 4, "names": { "uk": "Коти", "en": "Cats" } }
]
},
{
"key": "countries",
"items": [
{ "code": "804", "alpha2": "UA", "alpha3": "UKR",
"names": { "uk": "Україна", "en": "Ukraine", "ru": "Украина", "de": "Ukraine", "es": "Ukraine" } }
]
},
{
"key": "languages",
"items": [
{ "code": "uk", "native": "Українська",
"names": { "uk": "Українська", "en": "Ukrainian", "ru": "Украинский", "de": "Ukrainian", "es": "Ukrainian" } }
]
}
],
"metadata": { "etag": "W/\"dict-…\"", "generated_at": "2026-05-30T08:00:00+00:00", "languages": ["uk","en","ru","de","es"] },
"links": [],
"message": null
}| Field | Description |
|---|---|
payload[].key | Dictionary key. |
payload[].items[].code | Stable id used as the value in write endpoints. Numeric for most dictionaries (species, sex, …); for countries it is the zero-padded ISO 3166-1 numeric code as a string (e.g. "004", "804"); for languages it is the ISO 639-1 code (e.g. "uk"). |
payload[].items[].names | Map locale → localized name. Locales without a translation fall back to English. |
payload[].items[].alpha2 / alpha3 | Countries only: ISO 3166-1 alpha-2 / alpha-3 codes for convenient mapping. |
payload[].items[].native | Languages only: the language's own name (endonym), handy for language pickers. |
metadata.etag | Pass back in If-None-Match to get 304 when unchanged. |
metadata.languages | Active locales actually present in this build. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK — dictionaries returned. |
304 | Not Modified — your If-None-Match matches; reuse the cached copy. |
/v1/partner/ownersHalter anlegen (oder finden); gibt die globale Benutzer-ID zurück.▸Auth: HMAC. Any partner key. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
email | conditional | string | — | Owner email. One of email/phone is required — it is how the owner is later reached/authenticated. |
phone | conditional | string | — | Owner phone (E.164). One of email/phone is required. |
first_name | no | string | — | Given name. |
last_name | no | string | — | Family name. |
language | no | string | languages | Preferred locale. |
country | no | string | countries | Zero-padded ISO 3166-1 numeric code as a string (e.g. "804") — matches the countries dictionary code. |
consent | yes | object | — | Consent block (immutable audit). |
consent.account_creation | yes | bool | — | Must be true — the owner agreed to account creation. The time of capture is recorded server-side. |
Request body (JSON)
{
"email": "jane@example.com",
"phone": "+380681234567",
"first_name": "Jane",
"last_name": "Doe",
"language": "uk",
"country": "804",
"consent": {
"account_creation": true
}
}Example response
{
"payload": {
"user_gid": 90231,
"has_account": true,
"email": "jane@example.com",
"phone": null,
"display_hint": "Ол*** К.",
"language": "uk",
"country_id": 804
}
}| Field | Description |
|---|---|
user_gid | Global user id — pass it into POST /animals owners[].user_gid. |
has_account | Whether the owner already has a usable account. |
email | Email on file for this owner (null if unknown). |
phone | Phone on file for this owner (null if unknown). |
display_hint | Masked display name (no PII). |
Response statuses
| Status | Meaning |
|---|---|
201 | Created (or resolved an existing owner — idempotent by email/phone). |
409 | X-Eternity-Idempotency-Key reused with a different body, or still being processed. |
422 | Validation error (missing email/phone, or consent.account_creation not accepted). |
/v1/partner/owners/searchHalter anhand exakter E-Mail oder Telefonnummer finden.▸Auth: HMAC. Any partner key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
email_or_phone | yes | string | — | Exact email or phone (single field; email detected by format). |
Example response
{
"payload": { "user_gid": 90231, "has_account": true, "email": "jane@example.com", "phone": null, "display_hint": "Ол*** К.", "language": "uk", "country_id": 804 }
}| Field | Description |
|---|---|
user_gid | Global user id to reuse in animal registration. |
email | Email on file for this owner (null if unknown). |
phone | Phone on file for this owner (null if unknown). |
Response statuses
| Status | Meaning |
|---|---|
200 | Owner found. |
404 | No owner with that email_or_phone. |
422 | email_or_phone is required. |
/v1/partner/animalsTier registrieren (chip-basiert).▸Auth: HMAC. Vet/organization key. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
species | yes | int | species | Species dictionary id. |
is_microchip | yes | bool | — | Whether the animal is chipped. true → microchip is required; false → microchip is ignored and the registry assigns a temporary WC number. |
microchip | conditional | string | — | Microchip (transponder). Required only when is_microchip = true. |
nickname | yes | string | — | Animal name. |
qr_tag | no | string | — | QR passport serial to attach at registration. |
owners | no | array | — | Owners; first becomes main_owner, the rest owners (de-duplicated). Each entry either attaches an existing owner OR registers a new one inline. |
owners[].user_gid | conditional | int | — | Attach mode: global owner id (from POST/GET owners). Omit to register inline. |
owners[].email | conditional | string | — | Inline mode: owner email. One of email/phone required when there is no user_gid (upserted — no duplicates). |
owners[].phone | conditional | string | — | Inline mode: owner phone (E.164). |
owners[].first_name | no | string | — | Inline mode: given name. |
owners[].last_name | no | string | — | Inline mode: family name. |
owners[].language | no | string | languages | Inline mode: preferred locale. |
owners[].country | no | string | countries | Inline mode: zero-padded ISO 3166-1 numeric code as a string (e.g. "804") — matches the countries dictionary code. |
owners[].consent.account_creation | conditional | bool | — | Inline mode: must be true — the owner agreed to account creation. Required only when registering inline. |
breed | no | string | — | Breed (free text — no dictionary). |
color | no | string | — | Color (free text — no dictionary). |
gender_id | no | int | sex | Gender dictionary id. |
dob | no | date | — | Date of birth (ISO 8601). |
microchip_date | no | date | — | When the chip was implanted (ISO 8601). |
sterilization | no | bool | — | Sterilized flag. |
size | no | int | sizes | Size dictionary id. |
identifiers | no | array | — | Extra identifiers besides microchip/qr. |
identifiers[].type | yes | int | other_identifiers | Identifier type id from the other_identifiers dictionary (tattoo, ring, …). |
identifiers[].value | yes | string | — | Identifier value. |
identifiers[].added_at | no | date | — | When the identifier was assigned (ISO 8601). |
Request body (JSON)
{
"species": 3,
"is_microchip": true,
"microchip": "900263000123456",
"nickname": "Барсік",
"qr_tag": null,
"gender_id": 1,
"breed": "Labrador",
"color": "black",
"dob": "2022-03-01T00:00:00+00:00",
"microchip_date": "2022-11-20T00:00:00+00:00",
"sterilization": true,
"size": 2,
"owners": [
{ "user_gid": 90231 },
{
"email": "jane@example.com",
"phone": "+380681234567",
"first_name": "Jane",
"last_name": "Doe",
"consent": { "account_creation": true }
}
],
"identifiers": [
{ "type": 3, "value": "TAT-001", "added_at": "2026-05-01T00:00:00+00:00" }
]
}Example response
{ "payload": { "id": "8xK3pQzVnB7rL2qF" } }| Field | Description |
|---|---|
id | Unguessable public animal id (NanoID). Use it in all subsequent animal calls. |
Response statuses
| Status | Meaning |
|---|---|
201 | Animal registered. |
409 | X-Eternity-Idempotency-Key conflict (same key, different body). |
422 | Validation error: missing species/nickname/is_microchip; is_microchip=true without a valid microchip; a duplicate microchip (transponder already registered, field "transponder"); or an inline owner without email/phone or without consent.account_creation. |
/v1/partner/animals/by-identifier/{type}/{value}Suche nach einem bestimmten Kennzeichnungstyp. Immer ein Array.▸Auth: HMAC. Any partner key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
type | yes | path enum | — | microchip or qr_tag. |
value | yes | path string | — | Identifier value to look up. |
Example response
{
"payload": [
{ "id": "8xK3pQzVnB7rL2qF", "species": 3, "breed": "Labrador", "color": "black", "gender_id": 1,
"nickname": "Барсік", "microchip": "900263000123456", "qr_tag": null,
"dob": "2022-03-01", "register_date": "2026-05-30", "sterilization_status": true,
"lost_status": null, "deceased": false, "died_at": null, "status": 1 }
]
}| Field | Description |
|---|---|
payload | Array of animal cards — usually one, but a value may resolve to several. |
microchip / qr_tag | Active identifiers. |
lost_status | "active" when reported lost, otherwise null. |
deceased | True once euthanasia/death is recorded. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (possibly empty). |
/v1/partner/animals/by-identifier/{value}Suche über alle Kennzeichnungstypen gleichzeitig. Immer ein Array.▸Auth: HMAC. Any partner key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
value | yes | path string | — | Identifier value; searched across microchip and qr_tag. |
Example response
{ "payload": [ { "id": "8xK3pQzVnB7rL2qF", "nickname": "Барсік", "microchip": "900263000123456" } ] }Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (possibly empty). |
/v1/partner/animals/by-ownerTiere anhand des Halterkontakts suchen. Immer ein Array.▸Auth: HMAC. Any partner key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
email_or_phone | yes | string | — | Exact owner email or phone (single field; email detected by format). A phone is resolved to the owner first. |
Example response
{ "payload": [ { "id": "8xK3pQzVnB7rL2qF", "nickname": "Барсік", "species": 3 } ] }Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (empty if the owner has no email on file for phone-only lookups). |
422 | email_or_phone is required. |
/v1/partner/animals/{id}Vollständige Tierkarte.▸Auth: HMAC. Any partner key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path string | — | Animal public id (NanoID). |
Example response
{ "payload": { "id": "8xK3pQzVnB7rL2qF", "species": 3, "nickname": "Барсік", "microchip": "900263000123456", "deceased": false } }Response statuses
| Status | Meaning |
|---|---|
200 | OK. |
404 | Animal not found. |
/v1/partner/animals/{id}Änderbare Felder aktualisieren / als verstorben markieren.▸Auth: HMAC. Owner OR vet with an active relation to the animal. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
nickname | no | string | — | New name. |
color | no | string | — | New color (free text — no dictionary). |
sterilization_status | no | bool | — | Set sterilized flag. |
deceased | no | bool | — | true → mark the animal dead. |
Request body (JSON)
{
"nickname": "Барсік",
"color": "black",
"sterilization_status": true,
"deceased": false
}Example response
204 No Content
Response statuses
| Status | Meaning |
|---|---|
204 | Updated. |
403 | You are neither the owner nor a vet with a relation to this animal. |
409 | X-Eternity-Idempotency-Key conflict. |
422 | Validation error. |
/v1/partner/animals/{id}/proceduresBehandlungen erfassen; öffnet einen Besuch. Einzelobjekt oder Array.▸Auth: HMAC. Vet/organization key. Grants the vet a relation to the animal. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
(body) | yes | object | array | — | A single procedure object or an array of them (≤100). |
type | yes | int | procedure_types | Procedure catalogue id: 10 vaccination, 20 rabies vaccination, 30 transponder identification, 40 token identification, 50 deworming, 60 sterilization, 70 euthanasia / death certification. |
occurred_at | yes | datetime | — | When performed (ISO 8601). |
summary | no | string | — | Free-text note. |
revaccination_date | no | date | — | Override next-vaccination date (vaccinations). |
type_specific_payload | no | object | — | Per-type fields — validated server-side: 10/20 → {vaccine_name*, batch_number*}; 30 → {transponder_number* — 15 digits}; 40 → {token_number*}; 50 → {drug*, batch_number?, dose?}; 60 → {method?, anesthesia_type?}; 70 → {reason*, death_date*}. For type 30: if the animal already carries a microchip, it is kept — the new number is stored as an additional identifier (other_identifiers type 11) instead of replacing the chip. |
Request body (JSON)
[
{
"type": 10,
"occurred_at": "2026-05-30T08:00:00+00:00",
"summary": "Annual shot",
"revaccination_date": "2027-05-30",
"type_specific_payload": {
"vaccine_name": "Nobivac",
"batch_number": "A123"
}
},
{
"type": 30,
"occurred_at": "2026-05-30T08:05:00+00:00",
"type_specific_payload": {
"transponder_number": "900263000123456"
}
}
]Example response
{
"payload": {
"appointment_id": 7741,
"procedures": [
{ "id": 99001, "animal_id": "8xK3pQzVnB7rL2qF", "appointment_id": 7741, "procedure_type_id": 10,
"performed_at": 1748592000, "revaccination_date": "2027-05-30", "extra_fields": { "vaccine_name": "Nobivac", "batch_number": "A123" } }
]
}
}| Field | Description |
|---|---|
appointment_id | The visit opened/used for this batch. |
procedures[].id | Procedure record id. |
procedures[].procedure_type_id | Numeric type id (catalogue). |
procedures[].extra_fields | Stored type-specific payload. |
Response statuses
| Status | Meaning |
|---|---|
201 | Recorded; visit opened. |
409 | X-Eternity-Idempotency-Key conflict. |
422 | Unsupported type, missing occurred_at, or missing type-specific fields. |
/v1/partner/animals/{id}/proceduresBehandlungen eines Tieres auflisten. Immer ein Array.▸Auth: HMAC. Vet/organization key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
type | no | int | procedure_types | Filter by procedure catalogue id. |
since | no | datetime | — | Only on/after this time. |
until | no | datetime | — | Only on/before this time. |
Example response
{
"payload": [
{ "id": 99001, "animal_id": "8xK3pQzVnB7rL2qF", "visit_id": 7741, "type": 10,
"occurred_at": "2026-05-30T08:00:00+00:00", "summary": null, "revaccination_date": "2027-05-30",
"type_specific_payload": { "vaccine_name": "Nobivac", "batch_number": "A123" } }
]
}| Field | Description |
|---|---|
type | Procedure catalogue id (procedure_types dictionary). |
visit_id | Appointment the procedure was recorded under. |
type_specific_payload | Per-type fields. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (possibly empty). |
/v1/partner/procedures/{id}Einzelne Behandlung.▸Auth: HMAC. Vet/organization key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path int | — | Procedure record id. |
Example response
{ "payload": { "id": 99001, "type": 10, "occurred_at": "2026-05-30T08:00:00+00:00", "visit_id": 7741 } }Response statuses
| Status | Meaning |
|---|---|
200 | OK. |
404 | Procedure not found. |
/v1/partner/animals/{id}/photosFoto hochladen (multipart). Halter oder Tierarzt mit Beziehung.▸Auth: HMAC + multipart/form-data. Owner OR vet with relation. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
file | yes | file | — | Image file. Max 8 MB per photo (config partner.photos.max_single_mb). |
kind | no | enum | — | avatar | gallery | nose_print. avatar sets the main photo. Default gallery. |
Example response
{ "payload": { "id": 33015 } }| Field | Description |
|---|---|
id | New photo id. |
Response statuses
| Status | Meaning |
|---|---|
201 | Uploaded. |
403 | Not the owner or a vet with a relation. |
422 | Invalid file or larger than 8 MB (reduce size / lower quality). |
413 | Whole request exceeds 15 MB. |
/v1/partner/animals/{id}/photos/{photoId}Foto soft-löschen.▸Auth: HMAC. Owner OR vet with relation. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path string | — | Animal public id (NanoID). |
photoId | yes | path int | — | Photo id. |
Example response
204 No Content
Response statuses
| Status | Meaning |
|---|---|
204 | Deleted. |
403 | Not the owner or a vet with a relation. |