API methods
Partner API documentation
This reference describes how your integration talks to Animal-ID on behalf of your organization: authentication, required headers and the core MVP endpoints (owners, animals, procedures, photos, dictionaries).
SDKs & client libraries
Official open-source SDKs that wrap request signing and the endpoints below — drop them in instead of signing requests by hand.
PHP · Composer
Server-side PHP SDK: HMAC request signing and typed clients for every endpoint.
composer require animal-id/aid-partner-sdk
JavaScript · npm
Framework packages for Node and the browser, built on a shared core.
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.
Authentication & request signing
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; from 2026-07-04, animal registration attaches existing owners by public_id instead of user_gid. |
X-Eternity-Expand | Optional. JSON array of expand keys to embed extra data on supported endpoints (e.g. ["owners"]). See an endpoint's “Expansion” section for its allowed keys; unknown keys → 422. |
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);Webhooks
Receive deferred events — like an owner approving a vet's access request — as signed POST requests to your server. Set the URL and find your signing secret on your API keys tab.
Signed webhook deliveryWebhook signature & payload▸We POST every event to your configured webhook URL, signed with your per-app webhook secret (shown once when generated). Verify the signature before trusting a delivery by recomputing the HMAC over the canonical string and comparing in constant time:
canonical = "POST" + "\n" + path[?query] + "\n" + sha256_hex(rawBody) + "\n" + timestamp signature = hex( hmac_sha256(canonical, webhookSecret) )
| Header | Value |
|---|---|
X-Eternity-Webhook-Id | Unique delivery id (UUID); stable across resends of the same event. |
X-Eternity-Webhook-Event | The event key, e.g. animal_access.approved. |
X-Eternity-Webhook-Timestamp | Unix seconds when the delivery was signed; reject deliveries with a skewed timestamp. |
X-Eternity-Webhook-Signature | HMAC-SHA256 hex of the canonical string, keyed with your webhook secret (not your API private key). |
Delivery body (JSON)
{
"id": "5f1c0b8e-3a2d-4c7b-9b1a-2e6f0d4c8a91",
"event": "animal_access.approved",
"occurred_at": "2026-06-24T09:15:00+00:00",
"result": {
"animal_id": "8xK3pQzVnB7rL2qF",
"requester_user_gid": 90231,
"status": "granted",
"requested_at": "2026-06-23T09:00:00+00:00",
"expires_at": "2026-06-30T09:00:00+00:00",
"retry_after_seconds": 0,
"decided_at": "2026-06-24T09:15:00+00:00"
}
}| Field | Description |
|---|---|
id | The unique event id (matches the X-Eternity-Webhook-Id header). |
event | The event key, e.g. animal_access.approved. |
occurred_at | When the event occurred (ISO 8601). |
result | The access decision. Mirrors GET /v1/partner/animals/{id}/access-request and adds animal_id, requester_user_gid and decided_at. |
Events
| Key | Meaning |
|---|---|
animal_access.approved | An owner approved your pending access request — you may now edit the animal. |
animal_access.denied | An owner denied your access request — you can request again after it expires. |
Acknowledge with any 2xx response. A non-2xx status or a timeout is recorded as a failed delivery, which you can resend from the webhook delivery log in your cabinet.
const crypto = require('crypto');
// Express: mount with a raw-body parser so you verify the EXACT bytes received.
// app.post('/animal-id/webhook', express.raw({ type: 'application/json' }), handler)
function verify(req, webhookSecret) {
const ts = req.header('X-Eternity-Webhook-Timestamp');
const sig = req.header('X-Eternity-Webhook-Signature');
const path = req.originalUrl; // path (+ ?query) exactly as received
const raw = req.body; // Buffer of the raw request body
const bodyHash = crypto.createHash('sha256').update(raw).digest('hex');
const canonical = ['POST', path, bodyHash, ts].join('\n');
const expected = crypto.createHmac('sha256', webhookSecret).update(canonical).digest('hex');
return sig && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}<?php
// Verify a webhook delivery from Animal ID before trusting it.
function verifyWebhook(string $rawBody, string $path, string $webhookSecret): bool {
$ts = $_SERVER['HTTP_X_ETERNITY_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_ETERNITY_WEBHOOK_SIGNATURE'] ?? '';
$canonical = implode("\n", ['POST', $path, hash('sha256', $rawBody), $ts]);
$expected = hash_hmac('sha256', $canonical, $webhookSecret);
return $sig !== '' && hash_equals($expected, $sig);
}
$rawBody = file_get_contents('php://input'); // verify the EXACT bytes received
if (!verifyWebhook($rawBody, $_SERVER['REQUEST_URI'], getenv('AID_WEBHOOK_SECRET'))) {
http_response_code(401);
exit;
}
$event = json_decode($rawBody, true); // ['id' => …, 'event' => …, 'result' => …]
http_response_code(204); // acknowledge with any 2xxAPI methods
Start typing — sections filter by keyword.
/v1/partner/dictionariesReference dictionaries (multilang, filterable).▸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/ownersCreate (or resolve) an owner; returns the global user id.▸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,
"public_id": "V1StGXR8Z5jd",
"has_account": true,
"email": "jane@example.com",
"phone": null,
"display_hint": "Ол*** К.",
"language": "uk",
"country_id": 804
}
}| Field | Description |
|---|---|
public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
user_gid | Legacy numeric owner id (owner-attach on older API versions). |
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/searchFind an owner by exact email or phone.▸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, "public_id": "V1StGXR8Z5jd", "has_account": true, "email": "jane@example.com", "phone": null, "display_hint": "Ол*** К.", "language": "uk", "country_id": 804 }
}| Field | Description |
|---|---|
public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
user_gid | Legacy numeric owner id (owner-attach on older API versions). |
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/animalsRegister an animal (chip-based).▸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[].public_id | conditional | string | — | Attach mode (API version >= 2026-07-04): existing owner public_id from POST/GET owners. Omit to register inline. |
owners[].user_gid | conditional | int | — | Attach mode (API versions before 2026-07-04): legacy numeric owner id. Omit to register inline. |
owners[].email | conditional | string | — | Inline mode: owner email. One of email/phone required when there is no public_id/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": [
{ "public_id": "V1StGXR8Z5jd" },
{
"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}Lookup by a specific identifier type. Always an 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,
"abilities": { "can_edit": true } }
]
}| 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. |
Abilities
Every animal object carries an abilities object describing what the authenticated partner user may do with this animal. This is the first access flag; more will be added over time.
| Field | Description |
|---|---|
abilities.can_edit | Whether the authenticated partner user may edit this animal — update its data, add procedures and manage photos. True when the user registered the animal, has any relation to it (animal_user_relation), or is an active member of an organization whose registry holds it (org_animals). |
Expansion (X-Eternity-Expand)
Optional header carrying a JSON array of expand keys (e.g. ["owners"]). Each requested key embeds an extra field into every animal object in the response; omit the header to get the plain card. Unknown keys are rejected with 422.
| Key | Adds | Description |
|---|---|---|
owners | owners[] | The animal's owners — the main owner plus any co-owners — each with contact data and an is_main_owner flag. Available on this partner surface only. |
"owners": [
{
"user_gid": 90231,
"public_id": "V1StGXR8Z5jd",
"has_account": true,
"email": "jane@example.com",
"phone": "+380681234567",
"display_hint": "Ja*** D.",
"language": "uk",
"country_id": "804",
"is_main_owner": true
}
]| Field | Description |
|---|---|
owners[].public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
owners[].user_gid | Legacy numeric owner id (owner-attach on older API versions). |
owners[].has_account | Whether the owner already has a usable account. |
owners[].email | Email on file for this owner (null if unknown). |
owners[].phone | Phone on file for this owner (null if unknown). |
owners[].display_hint | Masked display name (no PII). |
owners[].language | Preferred locale. |
owners[].country_id | Zero-padded ISO 3166-1 numeric code as a string (e.g. "804"). |
owners[].is_main_owner | true for the main owner (animal_user_relation type main_owner); false for co-owners. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (possibly empty). |
/v1/partner/animals/by-identifier/{value}Lookup across all identifier types at once. Always an 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" } ] }Abilities
Every animal object carries an abilities object describing what the authenticated partner user may do with this animal. This is the first access flag; more will be added over time.
| Field | Description |
|---|---|
abilities.can_edit | Whether the authenticated partner user may edit this animal — update its data, add procedures and manage photos. True when the user registered the animal, has any relation to it (animal_user_relation), or is an active member of an organization whose registry holds it (org_animals). |
Expansion (X-Eternity-Expand)
Optional header carrying a JSON array of expand keys (e.g. ["owners"]). Each requested key embeds an extra field into every animal object in the response; omit the header to get the plain card. Unknown keys are rejected with 422.
| Key | Adds | Description |
|---|---|---|
owners | owners[] | The animal's owners — the main owner plus any co-owners — each with contact data and an is_main_owner flag. Available on this partner surface only. |
"owners": [
{
"user_gid": 90231,
"public_id": "V1StGXR8Z5jd",
"has_account": true,
"email": "jane@example.com",
"phone": "+380681234567",
"display_hint": "Ja*** D.",
"language": "uk",
"country_id": "804",
"is_main_owner": true
}
]| Field | Description |
|---|---|
owners[].public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
owners[].user_gid | Legacy numeric owner id (owner-attach on older API versions). |
owners[].has_account | Whether the owner already has a usable account. |
owners[].email | Email on file for this owner (null if unknown). |
owners[].phone | Phone on file for this owner (null if unknown). |
owners[].display_hint | Masked display name (no PII). |
owners[].language | Preferred locale. |
owners[].country_id | Zero-padded ISO 3166-1 numeric code as a string (e.g. "804"). |
owners[].is_main_owner | true for the main owner (animal_user_relation type main_owner); false for co-owners. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK — array (possibly empty). |
/v1/partner/animals/by-ownerLookup animals by owner contact. Always an 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 } ] }Abilities
Every animal object carries an abilities object describing what the authenticated partner user may do with this animal. This is the first access flag; more will be added over time.
| Field | Description |
|---|---|
abilities.can_edit | Whether the authenticated partner user may edit this animal — update its data, add procedures and manage photos. True when the user registered the animal, has any relation to it (animal_user_relation), or is an active member of an organization whose registry holds it (org_animals). |
Expansion (X-Eternity-Expand)
Optional header carrying a JSON array of expand keys (e.g. ["owners"]). Each requested key embeds an extra field into every animal object in the response; omit the header to get the plain card. Unknown keys are rejected with 422.
| Key | Adds | Description |
|---|---|---|
owners | owners[] | The animal's owners — the main owner plus any co-owners — each with contact data and an is_main_owner flag. Available on this partner surface only. |
"owners": [
{
"user_gid": 90231,
"public_id": "V1StGXR8Z5jd",
"has_account": true,
"email": "jane@example.com",
"phone": "+380681234567",
"display_hint": "Ja*** D.",
"language": "uk",
"country_id": "804",
"is_main_owner": true
}
]| Field | Description |
|---|---|
owners[].public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
owners[].user_gid | Legacy numeric owner id (owner-attach on older API versions). |
owners[].has_account | Whether the owner already has a usable account. |
owners[].email | Email on file for this owner (null if unknown). |
owners[].phone | Phone on file for this owner (null if unknown). |
owners[].display_hint | Masked display name (no PII). |
owners[].language | Preferred locale. |
owners[].country_id | Zero-padded ISO 3166-1 numeric code as a string (e.g. "804"). |
owners[].is_main_owner | true for the main owner (animal_user_relation type main_owner); false for co-owners. |
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}Full animal card.▸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, "abilities": { "can_edit": true } } }Abilities
Every animal object carries an abilities object describing what the authenticated partner user may do with this animal. This is the first access flag; more will be added over time.
| Field | Description |
|---|---|
abilities.can_edit | Whether the authenticated partner user may edit this animal — update its data, add procedures and manage photos. True when the user registered the animal, has any relation to it (animal_user_relation), or is an active member of an organization whose registry holds it (org_animals). |
Expansion (X-Eternity-Expand)
Optional header carrying a JSON array of expand keys (e.g. ["owners"]). Each requested key embeds an extra field into every animal object in the response; omit the header to get the plain card. Unknown keys are rejected with 422.
| Key | Adds | Description |
|---|---|---|
owners | owners[] | The animal's owners — the main owner plus any co-owners — each with contact data and an is_main_owner flag. Available on this partner surface only. |
"owners": [
{
"user_gid": 90231,
"public_id": "V1StGXR8Z5jd",
"has_account": true,
"email": "jane@example.com",
"phone": "+380681234567",
"display_hint": "Ja*** D.",
"language": "uk",
"country_id": "804",
"is_main_owner": true
}
]| Field | Description |
|---|---|
owners[].public_id | Stable public owner id — pass it into POST /animals owners[].public_id (API version >= 2026-07-04). |
owners[].user_gid | Legacy numeric owner id (owner-attach on older API versions). |
owners[].has_account | Whether the owner already has a usable account. |
owners[].email | Email on file for this owner (null if unknown). |
owners[].phone | Phone on file for this owner (null if unknown). |
owners[].display_hint | Masked display name (no PII). |
owners[].language | Preferred locale. |
owners[].country_id | Zero-padded ISO 3166-1 numeric code as a string (e.g. "804"). |
owners[].is_main_owner | true for the main owner (animal_user_relation type main_owner); false for co-owners. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK. |
404 | Animal not found. |
/v1/partner/animals/{id}Patch mutable fields / mark deceased.▸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 | No access to this animal — request it (POST /v1/partner/animals/{id}/access-request) and retry once the owner approves. |
409 | X-Eternity-Idempotency-Key conflict. |
422 | Validation error. |
/v1/partner/animals/{id}/access-requestAsk the animal's owner to grant you access.▸Auth: HMAC. Vet/organization key. · X-Eternity-Idempotency-Key required
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path string | — | Animal public id (NanoID). |
Example response
{
"payload": {
"status": "pending",
"requested_at": "2026-06-23T09:00:00+00:00",
"expires_at": "2026-06-30T09:00:00+00:00",
"retry_after_seconds": 604800
}
}| Field | Description |
|---|---|
status | "granted" — you already have access, no request was created; "pending" — awaiting the owner; "denied" — the owner declined (re-considerable until it expires). |
requested_at | When the request was raised (null when status="granted"). |
expires_at | When the request expires and you may request again (null when status="granted"). |
retry_after_seconds | Seconds until you may request the same animal again (0 once elapsed; null when status="granted"). |
Response statuses
| Status | Meaning |
|---|---|
201 | A new access request was created — the owner has been notified by email. |
200 | No new request created: you already have access (status "granted"), or an active request already exists (status "pending"/"denied") — wait retry_after_seconds before re-requesting. |
404 | Animal not found. |
/v1/partner/animals/{id}/access-requestCheck whether your access request was approved.▸Auth: HMAC. Vet/organization key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path string | — | Animal public id (NanoID). |
Example response
{
"payload": {
"status": "granted",
"requested_at": "2026-06-23T09:00:00+00:00",
"expires_at": "2026-06-30T09:00:00+00:00",
"retry_after_seconds": 0
}
}| Field | Description |
|---|---|
status | "granted" — access has been approved and is active; "pending" — awaiting the owner; "denied" — declined; "none" — no active request. |
Response statuses
| Status | Meaning |
|---|---|
200 | OK. |
404 | Animal not found. |
/v1/partner/animals/{id}/proceduresRecord procedures; opens a visit. Single object or array.▸Auth: HMAC. Vet/organization key with access 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", "visit_id": 7741, "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" } }
]
}
}| Field | Description |
|---|---|
appointment_id | The visit opened/used for this batch. |
procedures[] | The recorded procedures — same card as GET list/show. |
procedures[].id | Procedure record id. |
procedures[].type | Numeric type id (procedure_types dictionary). |
procedures[].type_specific_payload | Stored type-specific payload. |
Response statuses
| Status | Meaning |
|---|---|
201 | Recorded; visit opened. |
403 | No access to this animal — request it (POST /v1/partner/animals/{id}/access-request) and retry once the owner approves. |
404 | Animal not found. |
409 | X-Eternity-Idempotency-Key conflict. |
422 | Unsupported type, missing occurred_at, or missing type-specific fields. |
/v1/partner/animals/{id}/proceduresList an animal's procedures. Always an 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). |
404 | Animal not found. |
/v1/partner/procedures/{id}Single procedure.▸Auth: HMAC. Vet/organization key.
Request fields
| Field | Req. | Type | Dictionary | Description |
|---|---|---|---|---|
id | yes | path int | — | Procedure record id. |
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" }
}
}Response statuses
| Status | Meaning |
|---|---|
200 | OK. |
404 | Procedure not found. |
/v1/partner/animals/{id}/photosUpload a photo (multipart). Owner or vet with relation.▸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 | No access to this animal — request it (POST /v1/partner/animals/{id}/access-request) and retry once the owner approves. |
422 | Invalid file or larger than 8 MB (reduce size / lower quality). |
413 | Whole request exceeds 15 MB. |
/v1/partner/animals/{id}/photos/{photoId}Soft-delete a photo.▸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 | No access to this animal — request it (POST /v1/partner/animals/{id}/access-request) and retry once the owner approves. |