API reference

CodexPetHub API

Public read API and Agent Publish API for CodexPetHub — the agent-first registry for OpenAI Codex pets. Most endpoints are read-only and unauthenticated; the `/api/v1/agent/*` write surface is documented separately at `/docs/publish-with-codex` and uses either short-lived publish-intent Bearer tokens or long-lived `CPH1` HMAC keys.

Authentication

The public read API is unauthenticated. The Agent Publish API uses one of two schemes:

  • Authorization: Bearer cph_pub_… — single-use publish-intent token (expires 45 min after issuance, one submission max).
  • Authorization: CPH1 key_id:signature — long-lived HMAC for trusted agents. Requires X-CPH-Timestamp, X-CPH-Body-SHA256, and an optional Idempotency-Key.

Errors

Every error response uses a stable envelope: { "error": { "code", "message", "request_id" } }. The code field is one of:

  • bad_request
  • unauthorized
  • forbidden
  • not_found
  • rate_limited
  • expired_intent
  • consumed_intent
  • revoked_intent
  • wrong_state
  • idempotency_conflict
  • seo_revision_conflict
  • quota_exceeded
  • unsupported_role
  • unsupported_mime
  • missing_required_role
  • duplicate_role
  • too_many_files
  • size_too_large
  • validation_failed
  • server_error

Endpoints

  • GET /api/v1/health

    Service health probe

    Returns `{ ok: true, version, time }`. Always public, never rate-limited. Used by uptime monitors and the install scripts to verify reachability.

    Tag: Meta

  • GET /api/v1/pets

    List published pets

    Cursor-paginated list of published pets. Drafts and hidden pets are never returned. **Rate limit:** 30 requests/minute/IP for anonymous callers (Cloudflare WAF). **Caching:** server-side KV cache keyed by query parameters (5 min); response carries `Cache-Control: public, max-age=60, s-maxage=300`.

    Tag: Pets

  • GET /api/v1/pets/{slug}

    Fetch a single pet's full metadata

    Returns the full pet record (metadata, asset URLs, manifest/prompt URLs, sprite states). Drafts and hidden pets return 404 to avoid leaking existence. **Rate limit:** shares the 30/min/IP budget with `GET /api/v1/pets`.

    Tag: Pets

  • GET /api/v1/pets/{slug}/install-manifest.json

    Install manifest for a pet

    Server-rendered install manifest consumed by the `npx --yes codexpethub install <slug>` CLI (and any other agent that wants to install pets). The manifest is the only contract Codex uses to install a pet — it pins SHA-256 hashes for every file, locks the schema version, and asserts the security flags (`verify_hashes_required`, `execute_code: false`, `allow_external_urls_in_pet_json: false`). The `codexpethub` skill is publish-only and does not consume this endpoint. **Caching:** `Cache-Control: public, max-age=300, s-maxage=3600`. KV-cached for 1 hour.

    Tag: Pets

  • GET /api/v1/pets/{slug}/prompt.txt

    Install prompt (plain-text alias)

    Convenience alias under `/api/v1/*` for the install prompt published at `/prompts/install/{slug}.txt`. Returns `text/plain; charset=utf-8` containing the multi-line “Copy for Codex” prompt the user pastes into Codex; the prompt instructs Codex to run `npx --yes codexpethub install <slug>` on the user's behalf, watch the SHA-256 verification line, and explain how to refresh custom pets in Codex Settings → Appearance → Pets. **Caching:** `Cache-Control: public, max-age=300, s-maxage=3600`.

    Tag: Pets

  • GET /api/v1/facets

    Faceted filter counts for the current query

    Returns live per-axis counts (kinds, vibes, tags) plus a single `total` for the homepage and `/pets` filter toolbars. Each axis's counts apply every filter *except* its own, so the user can always switch within an axis without resetting their selection. `kinds` is pre-seeded with every allowed value (`creature`, `character`, `object`) so the toolbar renders a stable chip set even when an axis has zero pets. **Rate limit:** shares the 60/min/IP budget with the other read endpoints under `/api/v1/`. **Caching:** server-side KV cache keyed by the normalized query (5 min TTL); response carries `Cache-Control: public, max-age=60, s-maxage=300` and `x-cph-cache: hit|miss`.

    Tag: Pets

  • GET /api/v1/tags

    List all tags with pet counts

    Returns every tag attached to at least one published pet, with the number of pets per tag and links to the SSR + JSON listing endpoints. **Caching:** `Cache-Control: public, max-age=300, s-maxage=3600`.

    Tag: Tags

  • GET /api/v1/tags/{tag}/pets

    Pets carrying a tag

    Returns the same shape as `GET /api/v1/pets?tag=…`, scoped to the path-parameter tag. Cursor-paginated, drafts hidden.

    Tag: Tags

  • GET /api/v1/collections

    List published collections

    Returns curated multi-pet collections. Each entry exposes its install manifest + install prompt URLs.

    Tag: Collections

  • GET /api/v1/collections/{slug}

    Fetch a single collection

    Returns collection metadata plus the ordered pet list. Each pet entry mirrors the top-level pet summary so the response is self-contained.

    Tag: Collections

  • GET /api/v1/collections/{slug}/install-manifest.json

    Aggregate install manifest for a collection

    Returns the same security envelope as a per-pet install manifest, but with an embedded `pets[]` array containing each pet's full install manifest in collection order. **Caching:** `Cache-Control: public, max-age=300, s-maxage=3600`.

    Tag: Collections

  • GET /api/v1/analytics/summary

    Read aggregated site analytics

    Returns a narrow, read-only Cloudflare Web Analytics summary for owner-controlled agents. Agents authenticate to this Worker with a dedicated analytics Bearer token; the Worker keeps the Cloudflare API token in secrets and returns only aggregate totals/top paths. The endpoint never exposes Cloudflare credentials, account settings, DNS, Workers, R2, D1, or write-capable APIs. **Caching:** Worker KV caches the sanitized summary for 15 minutes. Response headers are `Cache-Control: no-store` so agent/client copies are not cached.

    Tag: Analytics

  • GET /api/v1/agent/seo/backlog

    Read the agent SEO backlog

    Returns a bounded queue of published pets whose SEO copy is new, weak, or stale. Requires a `CPH1` trusted-agent key with `pets:seo_read`. The endpoint never returns Cloudflare credentials or write-capable account data.

    Tag: Agent SEO

  • GET /api/v1/agent/seo/stats

    Read accepted SEO revision counts

    Returns factual accepted SEO revision counts from `pet_seo_revisions` for one UTC date. Requires a `CPH1` trusted-agent key with `pets:seo_read`; this endpoint is intended for health checks and runners that need an audit-table source of truth.

    Tag: Agent SEO

  • POST /api/v1/agent/seo/batch

    Apply a safe batch of pet SEO updates

    Updates only allowlisted SEO fields on published pets: description, tags, vibes, kind, and related slugs. Requires `CPH1` with `pets:seo_update`; publish-intent Bearer auth is rejected. The server validates descriptions, recalculates `search_text`, enforces `expected_seo_revision`, writes `pet_seo_revisions`, and invalidates public KV caches.

    Tag: Agent SEO

  • POST /api/v1/reports

    Report a published pet for abuse

    Public abuse-report intake. Body must include the pet slug, a reason from a closed enum, an optional details string (≤500 chars), and a Cloudflare Turnstile token (`expectedAction: "report"`). **Rate limit:** 5 reports per hour per IP (Cloudflare WAF + Durable Object backstop). **Auto-hide:** when a pet's `report_count` crosses the threshold while published, its status flips to `hidden` pending admin review. The response intentionally omits any signal of whether the auto-hide was tripped so reporters cannot probe the threshold. Reporter identity is captured only as a peppered hash of the IP — raw IPs are never stored.

    Tag: Abuse

  • POST /api/v1/publish-intents

    Mint a single-use publish intent (web flow)

    Public endpoint that issues a `cph_pub_…` Bearer token. The caller must present a valid Cloudflare Turnstile token (`expectedAction: 'publish_intent'`). The returned token is single-use: the active → consumed transition runs atomically when the first `POST /api/v1/agent/submissions` claims it, before the submission row is durably written, so a 5xx returned after consumption leaves the intent consumed and the caller must mint a fresh intent. Subsequent calls to `/complete` and `GET /agent/submissions/{id}` continue to authenticate against the originating submission until the 45-minute TTL elapses. **Rate limit:** 1 active intent per IP, 3 per hour, 10 per day (Cloudflare WAF + Durable Object backstop). Only `CF-Connecting-IP` is trusted for IP attribution; `X-Forwarded-For` is ignored. Does not authenticate; the client identifier is the Turnstile challenge solution.

    Tag: Publish

  • POST /api/v1/agent/submissions

    Create a submission and request upload URLs

    Creates a submission row and returns presigned R2 PUT URLs for the declared files. Authenticated either as a publish-intent Bearer token (single-use) or a `CPH1` HMAC key (long-lived). The body must declare exactly the files the agent intends to upload (`pet.json` + `spritesheet.webp`); the validator re-measures and re-hashes after upload. **Single-use enforcement (publish-intent path):** the intent token is consumed atomically as part of this call — the active → consumed transition is the synchronization point that prevents two concurrent submissions from sharing one intent, and runs before the submission row is durably written. A 5xx returned after consumption leaves the intent consumed; callers retry with a fresh intent. A second `POST /api/v1/agent/submissions` with the same bearer returns `410 consumed_intent`. **Idempotency:** trusted-agent callers MAY send `Idempotency-Key: <opaque>`; replays of the same body return the cached response. A different body for the same key returns `409 idempotency_conflict`. **Quota:** trusted agents are subject to a per-key daily submission quota (default 100/day). Public publish-intent callers cap implicitly via the single-use token.

    Tag: Publish

  • GET /api/v1/agent/submissions/{id}

    Read submission status (owner-only)

    Returns the submission's current state, validator output (when available), and the public status URL. The caller must own the submission via the same auth credential used to create it; mismatched callers see `404 not_found` to avoid leaking submission existence.

    Tag: Publish

  • POST /api/v1/agent/submissions/{id}/complete

    Mark uploads complete and enqueue validation

    Transitions a submission from `awaiting_uploads` to `queued_validation` and enqueues a `validate_submission` message. For publish-intent callers, the intent token is already single-use — it is consumed atomically when `POST /api/v1/agent/submissions` first creates the submission, so a second `POST /api/v1/agent/submissions` with the same bearer returns `410 consumed_intent`. `/complete` itself accepts a consumed intent that owns this specific submission (the agent's normal upload → complete path); replaying `/complete` after success returns `409 wrong_state` because the submission has already moved past `awaiting_uploads`.

    Tag: Publish