Skip to main content

Pet Safety

Three related surfaces help an owner protect a pet outside the clinic:

  1. Collar tag — a public profile reachable via a slug (/p/{slug}) the owner can print on a physical tag.
  2. Lost-pet mode — toggled on the same profile; broadcasts a "Help me get home" message to anyone who scans the tag.
  3. Pet passport PDF — a portable PDF with photo, vital stats, vaccinations, and a QR pointing back to the public profile.

Collar tag (public profile)

Every pet has an optional public_slug and a public_profile_enabled boolean. When the owner enables the profile from vetty:

  • POST /api/v1/owner/me/pets/{pet}/public-profile mints a slug if missing and sets public_profile_enabled=true.
  • The owner can disable any time: DELETE /api/v1/owner/me/pets/{pet}/public-profile.

Public surfaces

  • GET /p/{slug} (HTML, in routes/web.php) renders a landing page with the pet's name, photo, owner-supplied notes, and a contact CTA.
  • GET /api/v1/p/{slug} (JSON) is the same data as a structured payload — used by deep-linked app screens.

Privacy stance

The public profile only surfaces fields the owner explicitly opts into. Today the controller falls back to pet->client->phone when no profile-level phone is set — the audit calls this out as a P0 leak (see AUDIT.md §7) and the recommended fix is:

  • Require lost_mode_enabled_at before exposing any phone number.
  • Remove the fallback to the client's phone.
  • Rate-limit /p/{slug} and the JSON variant.

Lost-pet mode

Same profile, two extra columns: lost_mode_enabled_at and lost_mode_message.

Owner flow

  • POST /api/v1/owner/me/pets/{pet}/public-profile/lost — toggles lost-mode on, accepts a message body. The public landing page now shows the message in red, a "Last seen" date if supplied, and a click-to-reveal contact button.
  • DELETE /api/v1/owner/me/pets/{pet}/public-profile/lost — owner marks the pet as found.

Finder flow

A passerby finds the pet, scans the QR on the collar tag, lands on /p/{slug}. They see the lost-pet message and a contact action (phone link / email link). The owner gets the inbound contact via their own number / email — Vetty doesn't intermediate the conversation.

Pet passport PDF

GET /api/v1/owner/me/pets/{pet}/passport.pdf (auth'd). Generated with dompdf. Single-shot — no caching — so the latest vaccinations / photo always appear.

The PDF includes:

  • Pet header (name, species, breed, sex, DOB, weight, color, microchip if recorded).
  • A QR code that resolves to /p/{slug} (only when public_profile_enabled=true).
  • A vaccination table (administered, manufacturer/batch, next due) read from pet_vaccinations.
  • Owner contact (only when the public profile is enabled).
  • Allergies and medical flags.

Owners typically download this for international travel or when boarding a pet at a new facility.

A different but adjacent PDF: GET /api/v1/owner/me/pets/{pet}/vaccination-certificate.pdf. Optional ?vaccination_id= for a single-vaccine certificate (e.g. rabies for travel) or omit it for the full history. Generated by the same OwnerPortalController.

Architectural notes

  • Storage — none of these surfaces store generated PDFs to disk; they stream the generated bytes per request.
  • Audit hooks — toggling lost-mode and enabling/disabling the public profile go through Auditable so the timeline shows who changed what.
  • Tenants are uninvolved — these are owner-level features. Even though the pet always has a tenant_id (the most recent clinic that wrote a record for it), public visibility is decided by the owner's toggle alone.

Roadmap

  • Geo-fenced lost-pet alerts — community opt-in radius pings to nearby Vetty owners.
  • Cross-tenant medical-record portability with explicit owner consent (so a passport PDF includes Rx history from any clinic the pet has visited).
  • Photo carousel on the public profile.
  • Print-ready collar-tag QR sticker template.