We get asked this a lot: "Can I pull our pet data into our own website?" Or "Can we automatically create adopter records from our external form?" Or "We want to build a custom adoption page that matches our brand."
The answer used to be "sort of, with the embed widgets." Now it's just yes.
We published pawplacer-sdk — a TypeScript/JavaScript client for the PawPlacer API. It's on npm, it's typed end to
end, and it lets you read and write your shelter data from your own code. Server-side only, so your API key stays safe.
What you can do with it
Build a custom pet listing on your website. Pull your available animals with filters (species, status, search) and display them however you want. Full control over the design. Update automatically when you change something in PawPlacer.
Create records from external forms. If you use a third-party form tool or your own website form, you can create adopter, foster, or pet records in PawPlacer automatically when someone submits.
Sync with other systems. Use updated_since to pull only records that changed since your last sync. Build nightly
jobs that keep PawPlacer in sync with whatever else your organization uses.
Display adoption fees dynamically. Pull your fee configuration (species rules, attribute adjustments) and show accurate fees on your website without hardcoding them.
Show contracts. Pull your adopter, foster, volunteer, or surrender contracts as markdown and render them on your site.
Getting started
Install it:
npm install pawplacer-sdk
Generate an API key in Settings > API. There are two types: read keys for websites and dashboards, write keys for backend jobs that create records. Use the right one for what you're doing.
Then set up a client:
import { PawPlacerClient } from "pawplacer-sdk";
const pawplacer = new PawPlacerClient({
apiKey: process.env.PAWPLACER_API_KEY!,
});
That's it. Now you can do things like:
// Get available dogs
const dogs = await pawplacer.pets.list({
status: "available",
species: "dog",
limit: 12,
});
// Get a specific pet
const pet = await pawplacer.pets.get("pet-uuid");
// List active adopters
const adopters = await pawplacer.people.list({
type: "adopter",
status: "active",
});
// Create an adopter from your own form
const newAdopter = await pawplacer.people.create({
type: "adopter",
name: "Jane Smith",
email: "jane@example.com",
status: "pending",
});
Every response is fully typed. Your editor knows what Pet, Person, AdoptionFeeEntry look like. No guessing.
Custom fields work too
If you've set up custom form fields in PawPlacer (yard size, vet references, whatever), the SDK can read those definitions and include custom field data when creating records:
// See what custom fields exist
const fields = await pawplacer.pets.getCustomFields();
// Create a pet with custom field data
const pet = await pawplacer.pets.create({
name: "Max",
species: "dog",
age_category: "young",
sex: "male",
size: "medium",
status: "available",
health: "good",
custom_field_data: {
microchip_brand: "HomeAgain",
rescue_origin: "owner_surrender",
},
});
Built-in caching
GET responses are cached in memory with stale-while-revalidate, so if you're building a website that shows the same pet listing to every visitor, you're not hammering the API on every page load. The default refresh is every 3 hours, but you can tune it:
const pawplacer = new PawPlacerClient({
apiKey: process.env.PAWPLACER_API_KEY!,
cache: { enabled: true, refreshFrequency: 60 },
});
You can also clear the cache manually, invalidate specific patterns, or check hit/miss stats. The API's
Cache-Control headers take precedence when present.
Idempotency for create operations
Every create call sends an idempotency key automatically, so if a request gets retried (network hiccup, timeout),
you don't end up with duplicate records. For background jobs that might replay across restarts, you can pass your own
stable key:
const pet = await pawplacer.pets.create(
{ name: "Max", species: "dog", /* ... */ },
{ idempotencyKey: `nightly-sync:${externalId}` },
);
If the key was already used with the same payload, you get the original response back.
pawplacer.lastResponseMeta.idempotencyReplay tells you if that happened.
Rate limits and error handling
Rate limits are per API key and vary by endpoint. The SDK exposes them through pawplacer.lastResponseMeta.rateLimit
so you can see how many requests you have left. Listing endpoints get 100 requests/hour, individual gets are 400/hour,
and create endpoints are 10/hour.
Errors are typed too. PawPlacerApiError for HTTP errors (with status, code, and message),
PawPlacerResponseValidationError if a response doesn't match the expected shape. Both are importable from the
package.
Works with Next.js, Express, or whatever
The SDK is server-side only by default — it blocks browser usage to keep your API key safe. Use it in API routes, server actions, getServerSideProps, Express handlers, cron jobs, whatever runs on a server.
// Next.js App Router example
// app/api/pets/route.ts
import { PawPlacerClient } from "pawplacer-sdk";
export async function GET() {
const client = new PawPlacerClient({
apiKey: process.env.PAWPLACER_API_KEY!,
});
const pets = await client.pets.list({
status: "available",
limit: 12,
});
return Response.json(pets);
}
CommonJS works too if that's your setup.
Why we built this
Shelter websites are important. They're often the first thing a potential adopter sees. But a lot of shelters are stuck with whatever template their website platform offers, and keeping pet listings updated across two systems is a pain that most volunteer-run organizations don't have time for.
The SDK lets you build exactly what you want — or hire someone to build it — and have it stay in sync with PawPlacer automatically. Update a pet's status in PawPlacer and it's reflected on your website. Add a new animal and it shows up in your listing. No double entry, no copy-pasting, no forgetting to update the website for three weeks.
The full docs, OpenAPI spec, and example code are linked from the package readme on npm. Install it, grab an API key, and see what you can build.


