API Integration
This page documents the current external API contract and operational behavior for programmatic clients.
Contract Scope
Current canonical pet-management contract:
GET /api/my-petsGET /api/my-pets/sectionsPOST /api/petsGET /api/pets/{pet}PUT /api/pets/{pet}PUT /api/pets/{pet}/statusDELETE /api/pets/{pet}POST /api/pets/{pet}/weightsPUT /api/pets/{pet}/weights/{weight}DELETE /api/pets/{pet}/weights/{weight}POST /api/pets/{pet}/medical-recordsPUT /api/pets/{pet}/medical-records/{record}DELETE /api/pets/{pet}/medical-records/{record}POST /api/pets/{pet}/vaccinationsPUT /api/pets/{pet}/vaccinations/{record}POST /api/pets/{pet}/vaccinations/{record}/renewDELETE /api/pets/{pet}/vaccinations/{record}POST /api/pets/{pet}/microchipsPUT /api/pets/{pet}/microchips/{microchip}DELETE /api/pets/{pet}/microchips/{microchip}GET|POST /api/habitsGET|PUT|DELETE /api/habits/{habit}GET /api/habits/{habit}/heatmapGET|PUT /api/habits/{habit}/entries/{date}POST /api/habits/{habit}/archivePOST /api/habits/{habit}/restorePOST /api/pets/{pet}/photosPOST /api/pets/{pet}/photos/{photo}/set-primaryDELETE /api/pets/{pet}/photos/{photo}
Notes:
- The platform currently treats
/api/*as the active v1 contract. PATCH /api/pets/{pet}is not part of the external contract.- Success responses follow the standard envelope:
{ success, data, message? }. - Error responses follow:
{ success: false, data: null, message, error, errors? }(fielderrorsappears for validation failures). POST /api/petsrequirescountry(ISO 3166-1 alpha-2, e.g.VN).GET /api/my-petsandGET /api/my-pets/sectionsinclude a compacthealth_summaryon each pet for list views. This summary currently exposes latest and previous weight values plus aggregate vaccination status so clients can render pet cards without per-pet follow-up requests.
Authentication
Primary external auth is Sanctum personal access tokens (Bearer token).
Token permissions currently available:
pet:readpets:read(MCP pet-profile grants)pet:writehealth:readhealth:writehabits:readhabits:writemicrochips:readmicrochips:writesharing:readsharing:writeplacement:readplacement:writehelpers:readhelpers:writemessages:readmessages:writegroups:readgroups:writefinance:readfinance:writenotifications:readnotifications:writeprofile:readprofile:writeinvitations:readinvitations:writecreatereadupdatedelete
New manually created tokens default to read only. Existing user-created PATs retain the generic abilities. MCP exchange tokens instead receive only the independently consented domain abilities: pets:read, health:read, pet:write (from MCP scope pets:write), health:write, habits:read, habits:write, microchips:read, microchips:write, sharing:read, sharing:write, placement:read, placement:write, helpers:read, helpers:write, messages:read, messages:write, groups:read, groups:write, finance:read, finance:write, notifications:read, notifications:write, profile:read, profile:write, invitations:read, and/or invitations:write.
Token Management (SPA)
Developer UI route:
/developer
JSON endpoints used by the SPA:
GET /api/user/api-tokensPOST /api/user/api-tokensPUT /api/user/api-tokens/{tokenId}DELETE /api/user/api-tokens/{tokenId}
Security behavior:
- Token management is intentionally session-only. Personal access tokens cannot list, create, update, or revoke other tokens, even if they have broad abilities.
- Plaintext token is returned only once on creation.
- Plaintext token is never retrievable later.
- In the
/developerUI, newly created tokens are shown in a dedicated confirmation dialog with copy/download actions until the user confirms they saved the token.
Other browser identity and device plumbing is also session-only. Bearer personal access tokens cannot use email-verification management, connector-consent confirmation, impersonation, the legacy browser-user projection, push-device subscriptions, generic notification actions, or Telegram status/link/disconnect routes. These operations depend on a browser session, a device credential, or a separate identity proof and are not MCP abilities. Normal SPA cookie flows are unchanged.
Ability enforcement for PAT clients
The currently enforced programmatic contract is:
readforGET /api/users/mereadorpets:readforGET /api/my-petsreadorpets:readforGET /api/my-pets/sectionsreadorpets:readforGET /api/pets/{pet}readorhealth:readfor weight, medical-record, and vaccinationGETroutescreateorpet:writeforPOST /api/petsupdateorpet:writeforPUT /api/pets/{pet}updateforPUT /api/pets/{pet}/statusdeleteforDELETE /api/pets/{pet}createorhealth:writeforPOST /api/pets/{pet}/weightsupdateorhealth:writeforPUT /api/pets/{pet}/weights/{weight}deleteforDELETE /api/pets/{pet}/weights/{weight}createorhealth:writeforPOST /api/pets/{pet}/medical-recordsupdateorhealth:writeforPUT /api/pets/{pet}/medical-records/{record}deleteforDELETE /api/pets/{pet}/medical-records/{record}createorhealth:writeforPOST /api/pets/{pet}/vaccinationsupdateorhealth:writeforPUT /api/pets/{pet}/vaccinations/{record}createforPOST /api/pets/{pet}/vaccinations/{record}/renewdeleteforDELETE /api/pets/{pet}/vaccinations/{record}readorhabits:readfor habitGETroutescreateorhabits:writeforPOST /api/habitsupdateorhabits:writefor habit update, day-entry, archive, and restore routesdeleteorhabits:writeforDELETE /api/habits/{habit}updateorpet:writefor pet-photo upload and primary-photo routesdeleteorpet:writefor pet-photo deletionreadormicrochips:readfor microchipGETroutescreateormicrochips:writeforPOST /api/pets/{pet}/microchipsupdateormicrochips:writeforPUT /api/pets/{pet}/microchips/{microchip}deleteormicrochips:writeforDELETE /api/pets/{pet}/microchips/{microchip}readorsharing:readfor the narrowed pet-sharing, collaborator-suggestion, pending-invitation, and MCP body-token invitation-preview routescreate,update, ordelete(according to the legacy route) orsharing:writefor pet collaborator, invitation, and leave mutationsreadorplacement:readfor open-placement, request detail/context, and owner response-list readsreadorhelpers:readfor public/visible helper profiles plus country/city option readsreadormessages:readfor chat, message, and unread-count readscreate,update, ordelete(according to the legacy route) orplacement:writefor placement request, response, transfer, and finalization mutations; the legacy placementconfirm/rejectno-op routes are excludedcreate,update, ordelete(according to the legacy route) orhelpers:writefor own helper-profile, lifecycle, and photo mutationscreate,update, ordelete(according to the legacy route) ormessages:writefor placement-context direct chats, messages, explicit read receipts, own-message deletion, and leaving a chatreadorgroups:readfor group list/detail, member, pet, suggestion, and pending-invitation readscreate,update, ordelete(according to the legacy route) orgroups:writefor group lifecycle, membership, assigned-pet, and invitation mutationsreadorfinance:readfor currency, ledger, member, pet, configuration, dashboard, transaction, receipt, suggestion, pending-invitation, and MCP body-token ledger-invitation preview readscreate,update, ordelete(according to the legacy route) orfinance:writefor ledger lifecycle, membership, pet assignment, group-link, account/category configuration, transaction, receipt, and ledger-invitation mutationsreadornotifications:readfor notification inbox/unread summaries and delivery-preference readsupdateornotifications:writefor explicit notification read receipts and delivery-preference updatesreadorprofile:readfor the self-profile and owner-weight history readscreate,update, ordelete(according to the legacy route) orprofile:writefor display-name/avatar and own weight-history mutationsreadorinvitations:readfor sent onboarding-invitation and statistics readscreateordelete(according to the legacy route) orinvitations:writefor onboarding-invitation creation and revocation
Message listing is side-effect free. Clients use the explicit chat-read route when they intend to update read receipts. Message list/create responses expose updated_at for optimistic-concurrency deletion, and the chat-read response returns the exact chat_id and last_read_at receipt for post-write verification.
Multipart helper-profile updates include uploaded_photo_ids when photos were created. The field is stable under Idempotency-Key replay so clients can verify the exact uploaded media instead of guessing from collection order.
HTTP 409 responses expose stable data.code values where MCP must distinguish replay-key reuse (idempotency_conflict) from an existing active placement of the same type (active_placement_conflict).
MCP pet, group, and ledger invitation preview/accept/decline uses dedicated type-specific /api/mcp/*-invitations/* routes and carries the 64-character bearer token in the JSON body. This keeps it out of gateway, proxy, and API request paths and prevents accepting a different resource type through a generic endpoint. Browser invitation pages retain the public /api/resource-invitations/{token} contract.
Session-authenticated browser requests are not constrained by PAT abilities.
For tokens with groups:write, POST /api/groups serializes creates per user and rejects an equal normalized name among the caller's visible groups with HTTP 409 and stable data.existing_group_ids. Send allow_duplicate: true only for a deliberately distinct group. Idempotency replay resolves before the duplicate guard, so retrying the original request returns its original group.
For tokens with finance:write, POST /api/ledgers serializes creates per user and rejects an equal normalized title among the caller's visible ledgers with HTTP 409 and stable data.existing_ledger_ids. Send allow_duplicate: true only for a deliberately distinct ledger. Idempotency replay resolves before the duplicate guard. Ledger, account, category, transaction, membership, pet, group-link, and invitation mutations accept base_version from the documented target read when the target already exists; a stale version returns HTTP 409 without applying the update. Membership, pet, invitation, configuration, and transaction mutations advance the ledger version where needed for concurrency.
For tokens with the Phase 4B3 write abilities, notification writes are idempotent and can compare the previewed unread count or current delivery booleans before mutation. Profile writes accept base_version for display-name, avatar, and owner-weight targets; the stable owner-weight detail route supports read-before-write and verification. A distinct idempotency key for an existing owner-weight date returns duplicate_candidate. Email-targeted onboarding invitation creates detect a pending invitation for the same normalized address unless allow_duplicate: true records distinct intent. Invitation revocation accepts the invitation base_version. These abilities do not cover password change, account deletion, or notification action execution.
For tokens with pet:write, POST /api/pets serializes creates per user and rejects an exact case-insensitive name/pet-type duplicate with HTTP 409 and stable data.existing_pet_ids. Send allow_duplicate: true only for a deliberately distinct animal. An Idempotency-Key replay is resolved before the duplicate guard, so retrying the original request returns its original success. Pet, health, habit, photo, microchip, sharing, placement, helper, messaging, group, finance, profile, and invitation mutations accept base_version from the documented target read when the target already exists; a stale version returns HTTP 409 without applying the update. All MCP-exposed creates, updates, lifecycle changes, uploads, and deletes use Idempotency-Key. Multipart fingerprints use form fields plus file content hashes rather than transport boundaries, so an exact photo retry is replayable. Sharing changes also touch the pet's sharing version; invitation consume/revoke actions use the invitation version. The dedicated GET /api/pets/{pet}/sharing response excludes email addresses, history, and creator identifiers. Group membership and assigned-pet mutations advance the group version, while group invitation consumption uses the invitation version. Ledger membership, pet, invitation, configuration, and transaction mutations advance the ledger version similarly.
Phase 4A treats the documented notification, group, finance, self-profile, owner-weight, and sent onboarding-invitation reads as stable programmatic contracts. Phase 4B1 additionally makes the documented group and group- invitation mutations stable. Phase 4B2 makes the documented finance/ledger and ledger-invitation mutations stable. Other Phase 4 mutations remain outside MCP until their separate write-safety reviews.
Pet health reads remain available to unauthenticated callers where the pet's visibility permits it. An authenticated PAT caller must present read or the MCP-specific domain read ability:
GET /api/pets/{pet}/weightsGET /api/pets/{pet}/weights/{weight}GET /api/pets/{pet}/medical-recordsGET /api/pets/{pet}/medical-records/{record}GET /api/pets/{pet}/vaccinationsGET /api/pets/{pet}/vaccinations/{record}GET /api/pets/{pet}/microchipsandGET /api/pets/{pet}/microchips/{microchip}withmicrochips:read
Vaccination list accepts optional status=active|overdue|completed|all (default active). overdue returns incomplete renewals whose due_at calendar date is strictly earlier than today in the application timezone; it is a subset of active. Every vaccination payload includes authoritative boolean is_overdue — do not recompute overdue from local clocks or prose.
GPT Auth Bridge
GPT connector OAuth uses these bridge endpoints:
POST /api/gpt-auth/registerPOST /api/gpt-auth/telegram-linkPOST /api/gpt-auth/confirmPOST /api/gpt-auth/exchangePOST /api/gpt-auth/revoke
Important registration semantics:
- The connector does not provide a trusted email address or username from ChatGPT.
- During
/gpt-connect, the user entersnameandemaildirectly into the Meo Mai Moi registration form. - During
/gpt-connect, Google Sign-In returns to the same consent screen via a safe relativeredirectback to/gpt-connect?.... - During
/gpt-connect, Telegram Sign-In usesPOST /api/gpt-auth/telegram-linkto mint a short-lived resume token, then opens the bot with?start=login_<token>. After Telegram auth, the Mini App opens/gpt-connect?...&tg_token=...so the consent step can continue. - If email verification is required,
POST /api/gpt-auth/registerkeeps the account unverified and sends the normal verification email flow. - If email verification is disabled globally, GPT-registered users are marked verified immediately.
- GPT-issued Sanctum tokens are minted only after the authenticated user explicitly confirms the connection.
POST /api/gpt-auth/exchangeandPOST /api/gpt-auth/revokereturn401for an invalid connector API key, and503when the backend connector API key is not configured at all. The latter is treated as server misconfiguration and is logged.
Rate Limits
Rate limiting has two layers:
- Minute-based throttles (middleware)
- Daily user quota (business rule)
Minute-based examples:
- Authenticated API group:
throttle:authenticated(prod 60/min, dev/test/e2e 300/min) - Public listing endpoints:
throttle:public-api(prod 30/min, dev/test/e2e 300/min)
Daily quota:
- Regular users:
1000requests/day by default (configurable) - Premium users: unlimited
- Window boundary: UTC day (
00:00:00to23:59:59UTC)
Over-quota response:
- Status:
429 - Error code:
API_DAILY_QUOTA_EXCEEDED - Includes machine-readable quota metadata and
reset_at_utc
API Request Logging
API requests are persisted to api_request_logs for monitoring and support triage.
Logged fields include:
- Timestamp
- Method/path/route pattern
- Status code
- Auth mode (
pat,session,none) - User id (nullable)
- Quota-denied
429responses from the daily API quota middleware
Retention:
- Default: 30 days (configurable)
- Pruning command:
php artisan api-logs:prune - Scheduled daily via
routes/console.php
Configuration
Config defaults:
backend/config/api.php
Runtime-configurable settings (via system settings UI):
api_daily_quota_regularapi_request_logs_retention_days