Search OpenPets Codex Pet Registry
Purpose
Anonymously search the OpenPets community registry — the largest catalog of pixel-art "pets" (desktop companions) built for Codex, Claude Code, OpenCode, and Pi Code — and return ranked pet metadata plus a universal one-click install link suitable for handoff to the OpenPets macOS app. Read-only. No authentication required. Returns JSON.
When to Use
- A user wants pets matching free-text criteria ("show me cat pets", "find a dragon", "anime girl with a staff").
- A user wants to filter by pet kind:
animal,creature,object, orperson. - A user wants the most-popular, most-liked, or newest pets in the registry.
- A user wants a copy-pasteable install link to hand to the OpenPets macOS app (or share with a friend who has it installed).
- A user wants the preview spritesheet / share image URL for a pet to embed in a chat or doc.
Workflow
The OpenPets website exposes a public, paginated REST endpoint at /api/pets that powers the on-page gallery. Always prefer this over scripted browsing — no auth, no rate limits observed, returns the full record set including install-link material in one round trip.
-
Issue a single GET to the search endpoint:
GET https://openpets.sh/api/pets?q={query}&kind={kind}&sort={sort}&page={n}&pageSize={n} Accept: application/json -
Map user criteria to query params:
Param Type Required Notes qstring no Full-text search over displayName,description, andtags. URL-encode it.kindenum no One of animal,creature,object,person. Any other value returns zero results (total: 0).sortenum no One of new(default),popular,liked. Invalid values silently fall back tonew.pageint no 1-indexed. Default 1.pageSizeint no Default ~24. pageSize=100works; upper bound undocumented. -
Parse the JSON response. Top-level shape:
{ "page": 1, "pageSize": 24, "total": 239, "totalPages": 10, "generatedAt": "2026-05-18T11:13:49.412Z", "pets": [ ... ] } -
For each pet in
pets[], extract:id— slug used in URLs (e.g.qwq,apupepe,aobing)displayName,description,kind,ownerName,tags[]viewCount,downloadCount,likeCountpreviewUrl(relative/api/pets/{id}/preview) — animated WebP previewspritesheetUrl(relative/api/pets/{id}/spritesheet) — full sprite atlasshareImageUrl— 1200×630 OpenGraph card
-
Construct the install link for each pet:
https://openpets.sh/install/{pet.id}This URL is public and anonymous. Hitting it returns a
302with:Location: openpets://install?url=<signed-download-url-with-ticket>&id={pet.id}The signed
ticketis a server-issued JWT-style token with a ~24h expiry (exp,nonce,idpayload). The OpenPets macOS app intercepts theopenpets://protocol and pulls the bundle. Always hand out thehttps://openpets.sh/install/{id}URL — never try to construct theopenpets://deep-link yourself (the ticket is server-signed). -
Optional detail enrichment:
GET https://openpets.sh/api/pets/{id}returns{ "pet": { ... } }with the same record shape if you need a single record without searching. -
Return the ranked list with
id,displayName,description,kind,ownerName,tags, stats,previewUrl, andinstallUrl.
Browser fallback
If /api/pets ever becomes unreachable, drive the gallery UI directly. This is strictly slower (lazy-loaded sprites, IntersectionObserver pagination) but the contract is stable:
- Open
https://openpets.sh. Optionally pre-seed query state via URL params (?q=cat&kind=animal&sort=popular) — the gallery script reads them on load. - Fill
input#qwith the search term, setselect#sortandselect#kind. - Click
button[type="submit"]insideform#gallery-form. - Wait for
#gallery-resultsto populate. Each card is<article class="pet-card">with a link<a class="pet-card-preview-link" href="/pets/{id}">. - Read pet IDs from the
hrefattribute. Construct install URLs ashttps://openpets.sh/install/{id}. - To paginate, scroll
#gallery-sentinelinto view or click#load-more-pets.
No proxies or stealth required for either path — the site is behind Cloudflare but the API and gallery are CORS-open and bot-tolerant.
Site-Specific Gotchas
-
Two response shapes from
/api/petsdepending on sort order. Withsort=new(the default), the response is served from a registry mirror and includes lighter, mirror-flavored fields:installTicketUrl,downloadUrl,validationReport,source.apiBase,mirroredAt,registryNumber. Withsort=popularorsort=liked, the response is served live from the primary database and includes richer fields:spritesheetPath,ownerId,ownerHandle,likedByMe,reactionCounts,myReactions,ownerShadowbanned. Both share the core set (id,displayName,description,kind,ownerName,tags,uploadedAt,viewCount,downloadCount,likeCount). Don't depend on the extended fields being present. -
Invalid
kind⇒ empty results; invalidsort⇒ silent fallback.kind=fooreturnstotal: 0;sort=fooreturns the full registry sorted bynew. Validatekindagainst the enum before sending or you'll mislead the user with "0 matches". -
installTicketUrlanddownloadUrlin the response require auth.POST /api/pets/{id}/install-ticketreturns 404 anonymously, andGET /api/pets/{id}/downloadreturns 401{"error":"download ticket required"}. Ignore those fields for anonymous flows. The public/install/{id}redirect is the supported anonymous path; it generates the signed ticket server-side and embeds it in theopenpets://Location header. -
/install/{id}requires the OpenPets macOS app to actually resolve. Theopenpets://URI scheme is registered by the app from the alterhq/openpets release. Users on Linux/Windows, or macOS without the app, will see "no app to handle this URL". If unsure, prefix the response with a one-liner: "Install OpenPets first, then click the install links." -
Registry has two upstream sources. Pets mirrored from
codex-pets.net(the original Codex Pet Share, which OpenPets succeeded) carry asource.apiBasefield andmirroredAttimestamp; native OpenPets uploads don't. Useful for de-duping if a user uploads to both registries. -
Catalog size: ~3,500 pets as of May 2026. Default
pageSize=24;pageSize=100is fine. Don't paginate pasttotalPages— the gallery JS loops back to page 1 in that case, which would loop your scraper. -
Read-only via the API. Liking, favoriting, uploading, commenting all require a Cloudflare Access + Supabase session. Don't promise mutation flows from an anonymous client. For uploads point users to
/uploadin the OpenPets web app after signin. -
Pet IDs are author-chosen slugs (
qwq,aobing,apupepe,ro-job-female-runeknightdragon) — not UUIDs. They're URL-safe but can be long. Don't try to alphabetize them — they aren't ordered. -
No anti-bot wall observed. Cloudflare is configured permissively for the registry: anonymous
GET /api/pets, anonymousGET /api/pets/{id}, and anonymousGET /install/{id}all return 200/302 without challenges from datacenter IPs. Browser fallback also works without--verified/--proxiesflags, though the metadata in this skill was captured with both enabled as a defensive default.
Expected Output
Recommended return shape per query (one of three outcomes):
A — Successful search (q="cat"):
{
"query": { "q": "cat", "kind": null, "sort": "new", "page": 1, "pageSize": 24 },
"total": 239,
"totalPages": 10,
"pets": [
{
"id": "qwq",
"displayName": "qwq",
"description": "Neutral cool white-haired cat-tail pet: aloof outside, warm inside, cute, playful, and subtly enchanting.",
"kind": "person",
"ownerName": "taytaya",
"tags": [],
"uploadedAt": "2026-05-17T15:06:19.058Z",
"stats": { "views": 14, "downloads": 0, "likes": 0 },
"previewUrl": "https://openpets.sh/api/pets/qwq/preview",
"spritesheetUrl": "https://openpets.sh/api/pets/qwq/spritesheet",
"shareImageUrl": "https://openpets.sh/api/pets/qwq/share.png",
"detailUrl": "https://openpets.sh/pets/qwq",
"installUrl": "https://openpets.sh/install/qwq"
}
]
}
B — Kind-filtered popular search (kind="animal", sort="popular"):
{
"query": { "q": null, "kind": "animal", "sort": "popular", "page": 1, "pageSize": 2 },
"total": 2072,
"totalPages": 1036,
"pets": [
{
"id": "apupepe",
"displayName": "Pepe",
"description": "A compact Codex-style green frog pet in a plain blue shirt.",
"kind": "animal",
"ownerName": "kegashin",
"tags": ["cute", "animated", "pixel", "animal", "celeb", "mascot"],
"stats": { "views": 601, "downloads": 0, "likes": 5 },
"previewUrl": "https://openpets.sh/api/pets/apupepe/preview",
"spritesheetUrl": "https://openpets.sh/api/pets/apupepe/spritesheet",
"detailUrl": "https://openpets.sh/pets/apupepe",
"installUrl": "https://openpets.sh/install/apupepe"
}
]
}
C — Empty results (no matches):
{
"query": { "q": "unicornthatdoesnotexist", "kind": null, "sort": "new", "page": 1, "pageSize": 24 },
"total": 0,
"totalPages": 0,
"pets": []
}
For outcome C, surface to the user: "No OpenPets match your criteria. Try a broader term, drop the kind filter, or browse the newest pets at https://openpets.sh."