Skip to main content

Get Data

Retrieve pets, people, adoption fees, and contracts

In this guide

Key sections from this documentation page.

Use the PawPlacer public API to fetch account-scoped pet data, adopter, foster, surrender, or volunteer records, adoption fee rules, and contract terms. “Public API” describes the integration surface; these keyed read endpoints are not limited to records published on the organization's public profile. All endpoints require an API key sent in the x-api-key header. A Read only key from Settings > SDK/API is enough for every endpoint on this page.

Endpoint Summary

EndpointPurposeRate Limit
GET /api/petsPaginated pet list2,000/hr
GET /api/pets/{petId}Single pet5,000/hr
GET /api/pets/custom-fieldsCustom field metadata for pet forms50/hr
GET /api/people?type=adopter|foster|surrender|volunteerPaginated people list for one record type2,000/hr
GET /api/people/{id}?type=adopter|foster|surrender|volunteerSingle person record5,000/hr
GET /api/people/custom-fields?type=adopter|foster|surrender|volunteerCustom field metadata for one people form50/hr
GET /api/adoption-feesAdoption fee configuration rules50/hr
GET /api/contracts?type=adopter|foster|volunteer|surrenderTerms & conditions (markdown)50/hr

Cache responses for at least a few minutes to stay well below the hourly limits. The payloads are stable and work well with server-side or per-process caching.

OpenAPI 3.1 contract: https://pawplacer.com/openapi/public-api-v1.yaml

Standard Response Headers

All public GET endpoints include diagnostic and rate-limit headers:

  • X-Request-Id: unique request identifier for support/debugging
  • X-Api-Version: current public API contract version
  • X-Generated-At: response generation timestamp (ISO-8601)
  • X-RateLimit-Limit: endpoint hourly limit
  • X-RateLimit-Remaining: remaining calls in current window (-1 when unavailable)
  • X-RateLimit-Reset: Unix timestamp for next limit reset window
  • Retry-After: present on 429 responses

Conditional Requests (ETag)

All GET endpoints on this page return an ETag header containing a hash of the response payload. You can use this to avoid re-downloading data that hasn't changed.

Send the ETag value back as an If-None-Match header on your next request. If the data is unchanged, the API responds with 304 Not Modified and an empty body, saving bandwidth. The request still counts against the endpoint's hourly limit.

// First request: store the ETag
const response = await fetch('https://pawplacer.com/api/pets?species=dog', {
  headers: { 'x-api-key': process.env.PAWPLACER_API_KEY }
});
const etag = response.headers.get('ETag');
const pets = await response.json();

// Subsequent request: send If-None-Match
const check = await fetch('https://pawplacer.com/api/pets?species=dog', {
  headers: {
    'x-api-key': process.env.PAWPLACER_API_KEY,
    'If-None-Match': etag
  }
});

if (check.status === 304) {
  // Data unchanged: use your cached copy
} else {
  const freshPets = await check.json();
}

GET responses also include a Cache-Control: private, max-age=60, stale-while-revalidate=300 header, so browsers and other private HTTP-aware caches can reuse responses for up to 60 seconds with a 5-minute stale-while-revalidate window. Do not put keyed responses into a shared public cache.

GET /api/pets – List Pets

Returns a paginated list of non-deleted pets in the API key's account with a full pet profile payload, including custom fields and metadata. The list does not apply the public-profile show_public, public-status, photo, or organization-profile gates. Apply your own publishing filter before exposing API data to an unauthenticated website.

Request

GET https://pawplacer.com/api/pets
x-api-key: YOUR_API_KEY

Optional query parameters

  • limit: 1–100 (default 20)
  • offset: results to skip (default 0). Results are ordered newest first by created_at, then by id. A record created while you page through the list pushes later pages back by one, so de-duplicate by id.
  • species: dog, cat, rabbit, horse, small_furry, bird, barnyard, scales_fins_other, reptile, or invertebrate
  • status: matches your custom status labels (case-insensitive)
  • search: fuzzy match on name, description, and breed values
  • updated_since: ISO-8601 timestamp; returns only pets with updated_at >= updated_since

JavaScript example

const response = await fetch('https://pawplacer.com/api/pets?status=available&species=dog&limit=12', {
  headers: { 'x-api-key': process.env.PAWPLACER_API_KEY }
});

const { pets, total, hasMore } = await response.json();

Response

{
  "pets": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "name": "Max",
      "species": "dog",
      "status": "Available",
      "health": "good",
      "show_public": true,
      "breed": ["Labrador Retriever"],
      "color": ["Black"],
      "age_category": "young",
      "age_years": null,
      "age_months": null,
      "age_birthday": null,
      "sex": "male",
      "size": "medium",
      "description": "Friendly and energetic dog",
      "spayed": true,
      "adoption_fee": "250",
      "microchip_id": null,
      "good_with": ["families", "kids"],
      "bad_with": [],
      "temperaments": ["playful", "social"],
      "image_urls": [
        "https://pawplacer.com/pets/max.jpg",
        "https://pawplacer.com/pets/max-2.jpg"
      ],
      "image_url": "https://pawplacer.com/pets/max.jpg",
      "coat_length": "short",
      "custom_id": "EXT-1001",
      "custom_field_data": {
        "favorite_toy": "Tennis ball"
      },
      "intake_date": null,
      "outcome_date": null,
      "primary_veterinarian": "Downtown Vet Clinic",
      "special_needs": [],
      "tags": ["Senior", "Special Care"],
      "status_change_notes": null,
      "weight": null,
      "adopted_on": null,
      "adopted_by": "Jane Foster",
      "created_at": "2026-02-17T12:00:00.000000+00:00",
      "updated_at": "2026-02-17T12:00:00.000000+00:00"
    }
  ],
  "total": 42,
  "limit": 12,
  "offset": 0,
  "hasMore": true
}

Fields returned

  • pets: array of non-deleted account pets. Each object includes a full profile payload:
    • Core: id, name, species, status, health, show_public
    • Demographics: breed, color, age_category, age_years, age_months, age_birthday, sex, size, weight, spayed. age_category is the category the pet has reached today, worked out from age_birthday or the recorded years and months, rather than the value saved when the pet was entered. An age entered in years and months is stored as an estimated age_birthday, so age_years and age_months are empty for pets saved that way.
    • Behavior/compatibility: good_with, bad_with, temperaments, special_needs
    • Tags: tags (pet tag labels)
    • Identifiers: custom_id, microchip_id
    • Notes + dates: description, intake_date, outcome_date, status_change_notes
    • Relationships (resolved names): primary_veterinarian, adopted_by
    • Media: image_urls, image_url, coat_length
    • Custom data: custom_field_data (values of internal-only fields are omitted)
    • Surrender intake details are never returned: the reason for surrender, its urgency, where the pet was found, finding notes, and the links to the surrender record are left out of both custom_field_data and the top-level fields. This includes pet form fields of your own named like them, such as a custom Location found question.
    • Metadata: created_at, updated_at, adopted_on
  • total, limit, offset, hasMore: pagination metadata

GET /api/pets/{petId} – Single Pet

Fetch a single non-deleted pet in the API key's account by ID. This endpoint returns the same field set as the list endpoint and does not require the pet to be publicly visible.

Request

GET https://pawplacer.com/api/pets/{petId}
x-api-key: YOUR_API_KEY

JavaScript example

const response = await fetch(`https://pawplacer.com/api/pets/${petId}`, {
  headers: { 'x-api-key': process.env.PAWPLACER_API_KEY }
});

const pet = await response.json();

If the pet is deleted or does not exist in the API key's account, the API responds with 404 and { "error": "Pet not found" }. A private pet is still returned.

GET /api/pets/custom-fields – Custom Field Metadata

Retrieve configured custom form fields so you can send valid keys when creating pets.

Request

GET https://pawplacer.com/api/pets/custom-fields
x-api-key: YOUR_API_KEY

Response

{
  "custom_fields": [
    {
      "field_key": "favorite_toy",
      "label": "Favorite Toy",
      "field_type": "text",
      "required": false,
      "help_text": "Include any comfort objects",
      "placeholder": "Blanket or toy",
      "options": null,
      "sync_to_column": null,
      "template_id": "550e8400-e29b-41d4-a716-446655440010",
      "section_id": "550e8400-e29b-41d4-a716-446655440011",
      "section": "Behavior",
      "section_order": 2,
      "field_order": 1,
      "hidden": false,
      "internal_only": false
    }
  ]
}

Use the field_key (not the label) when populating custom_field_data in POST /api/pets or PATCH /api/pets/{petIdentifier} requests. Fields are returned in section_order and field_order. sync_to_column identifies standard record fields, while hidden and internal_only tell public renderers which fields to omit.

Rate Limit Errors

Rate limits are enforced per API key and endpoint on the current hourly window. If the limit is exceeded, the response is 429 with a descriptive error payload, for example:

{
  "error": "Rate limit exceeded. Maximum 2000 requests per hour. Cache responses to avoid throttling.",
  "code": "rate_limited",
  "request_id": "3dfabc8f-3f6f-4f4e-bffe-5d0dc5292e6f"
}

Reduce the frequency of calls or increase your cache window before retrying.

Error Reference

StatusReasonExample Payload
401Missing or invalid API key{ "error": "API key required", "code": "api_key_required", "request_id": "..." }
404Record not found{ "error": "Pet not found", "code": "pet_not_found", "request_id": "..." }
429Rate limit exceededsee example above
500Unexpected server issue{ "error": "Internal server error", "code": "internal_error", "request_id": "..." }

Always handle non-200 status codes and back off when you see 429 responses.

GET /api/people – List People

Returns a paginated list for one supported people record type. The type query parameter is required.

Request

GET https://pawplacer.com/api/people?type=adopter
x-api-key: YOUR_API_KEY

Query parameters

  • type: required: adopter, foster, surrender, or volunteer
  • limit: 1–100 (default 20)
  • offset: results to skip (default 0). Results are ordered newest first by created_at, then by id. A record created while you page through the list pushes later pages back by one, so de-duplicate by id.
  • status: filter by status (e.g. active, pending)
  • search: fuzzy match on name, email, phone
  • updated_since: ISO-8601 timestamp

Response

{
  "type": "adopter",
  "people": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "type": "adopter",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "555-0100",
      "address": "123 Main St",
      "status": "active",
      "status_change_notes": null,
      "custom_field_data": {},
      "tags": ["VIP"],
      "capacity": null,
      "created_at": "2026-03-01T10:00:00.000000+00:00",
      "updated_at": "2026-03-01T10:00:00.000000+00:00"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0,
  "hasMore": false
}

Volunteer records also include skills, interests, preferred_roles, and languages lists. When one of these is empty on the record, it is filled from the volunteer's answer to the Skills, Interests, Preferred Roles, or Languages question on their form. Internal-only questions are never read, and the answer also stays in custom_field_data.

GET /api/people/{id} – Single Person

GET https://pawplacer.com/api/people/{id}?type=adopter
x-api-key: YOUR_API_KEY

Returns the same person object shape as the list endpoint. The type parameter is required. Invalid UUIDs return 400. Missing records return 404.

GET /api/people/custom-fields – People Form Fields

GET https://pawplacer.com/api/people/custom-fields?type=adopter
x-api-key: YOUR_API_KEY

Response

{
  "type": "adopter",
  "custom_fields": [
    {
      "field_key": "has_yard",
      "label": "Has Yard",
      "field_type": "boolean",
      "required": false,
      "help_text": null,
      "placeholder": null,
      "options": null,
      "sync_to_column": null,
      "template_id": "550e8400-e29b-41d4-a716-446655440020",
      "section_id": "550e8400-e29b-41d4-a716-446655440021",
      "section": "Housing",
      "section_order": 1,
      "field_order": 4,
      "hidden": false,
      "internal_only": false
    }
  ]
}

Use these records to render the rescue's current form without hardcoded questions. Keep the returned section/field order, omit hidden and internal_only fields, use field_type, options, placeholder, and help_text for controls, and submit answers by field_key. For adopter and foster applications, render the matching GET /api/contracts content and then send the accepted application to POST /api/people.

The same four type values are supported here: adopter, foster, surrender, and volunteer.

GET /api/adoption-fees – Fee Configuration

Returns the account's adoption fee rules, one per species and attribute. An age rule is the base fee for pets of that species at that age (youngest, young, adult, senior). A health (sick, healthy), gender (male, female), or size (small, medium, large) rule is a percentage discount from 0 to 100.

GET https://pawplacer.com/api/adoption-fees
x-api-key: YOUR_API_KEY

Response

{
  "fees": [
    {
      "species": "dog",
      "attribute_type": "age",
      "attribute_value": "youngest",
      "adjustment": 350
    },
    {
      "species": "dog",
      "attribute_type": "health",
      "attribute_value": "sick",
      "adjustment": 20
    }
  ]
}

To price a pet the way PawPlacer does, start from the age rule for its species and the age category it has reached, then apply each matching discount to what remains, in the order health, sex, size, and round to a whole amount. A pet whose health is poor matches the sick rule; good and great match healthy. With the rules above, a sick dog in the youngest group costs 350 × 0.8 = 280. When the matching age rule is missing or 0, global_adoption_fee is null.

Pet responses also include a global_adoption_fee field (number or null) that resolves these rules against each pet's attributes when no manual fee override is set. The age rule uses the age category the pet has reached today, the same value as its age_category field.

GET /api/contracts – Terms & Conditions

Returns the terms & conditions content for a given contract type. Content is markdown. Organization variables in the template, such as {{organization.name}} and {{organization.phone}}, arrive filled in with the rescue's details; applicant variables such as {{adopter.name}} arrive blank, since your form collects them.

GET https://pawplacer.com/api/contracts?type=adopter
x-api-key: YOUR_API_KEY

Response

{
  "type": "adopter",
  "content": "# Adoption Agreement\n\nBy signing this agreement...",
  "updated_at": "2026-01-15T00:00:00.000000+00:00"
}

Valid types: adopter, foster, volunteer, surrender. If the rescue has not written its own terms for that type, content holds PawPlacer's default terms (the same ones its hosted forms show and record as accepted) and updated_at is null.

Keep reading

Move through the documentation in order.