{
  "x-draft": "DRAFT ONLY, not deployed. Merge into /workspace/gen/openapi.template.json after Jonathan approves. Price: C$3.99/month + tax.",
  "tags": [
    {
      "name": "Directory",
      "description": "Read-only, keyless pet directory data."
    },
    {
      "name": "Membership",
      "description": "Paid MapX Pet membership (C$3.99/month + applicable tax). Core benefits: all MapX Pet digital content, a member discount in the MapX Pet shop, and coupons at promoted partner places; supporting perk: ad-free browsing on mapxpet.com. Agents may start a signup; activation happens only after the human pays on a Stripe-hosted Checkout page."
    }
  ],
  "info_description_append": " Membership: agents can start a paid membership for their human with POST /api/v1/memberships (core benefits: digital content, shop discount, partner coupons; plus ad-free browsing). The human completes payment on Stripe Checkout before anything activates. Membership endpoints use a per-membership bearer token returned at signup; directory endpoints stay keyless.",
  "paths": {
    "/api/v1/membership/plans.json": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "getMembershipPlans",
        "summary": "Membership plans, price, entitlements and terms (static, no auth)",
        "security": [],
        "responses": {
          "200": {
            "description": "Plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    },
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plan"
                      }
                    },
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Entitlement"
                      }
                    },
                    "terms_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "privacy_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "pitch": {
                      "type": "string",
                      "description": "Short plain-language pitch to relay to the human; leads with the core benefits."
                    },
                    "how_to_join": {
                      "type": "string"
                    },
                    "stats_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                },
                "example": {
                  "plans": [
                    {
                      "id": "monthly",
                      "price": 3.99,
                      "currency": "CAD",
                      "interval": "month",
                      "price_is_placeholder": false,
                      "tax": "GST/HST/QST added at checkout based on the member's province"
                    }
                  ],
                  "pitch": "MapX Pet membership gives your human all MapX Pet digital content (recipes and pet-care guides), a member discount in the MapX Pet shop, and coupons at promoted partner places across Canada, plus ad-free browsing on mapxpet.com as a bonus. C$3.99/month plus tax, cancel anytime.",
                  "entitlements": [
                    {
                      "id": "digital_content",
                      "core": true,
                      "description": "All MapX Pet digital content: recipes, care guides and new releases"
                    },
                    {
                      "id": "shop_discount",
                      "core": true,
                      "description": "Member-only pricing in the MapX Pet shop when it opens (discount not yet decided)"
                    },
                    {
                      "id": "partner_coupons",
                      "core": true,
                      "description": "Exclusive offers at promoted partner vets, groomers, stores and pet-friendly places; one member code"
                    },
                    {
                      "id": "breeding_board",
                      "core": false,
                      "description": "Members-only breeding board: post breeding listings and contact other owners privately; anyone can browse"
                    },
                    {
                      "id": "pets_for_sale",
                      "core": false,
                      "description": "Pet for sale board: anyone browses listings; members see seller contact details and full listings, and can post"
                    },
                    {
                      "id": "ad_free",
                      "supporting": true,
                      "description": "Ad-free browsing on mapxpet.com (per browser, when ads are shown)"
                    }
                  ],
                  "how_to_join": "POST /api/v1/memberships; your human completes payment on Stripe Checkout",
                  "terms_url": "https://mapxpet.com/membership/terms/",
                  "privacy_url": "https://mapxpet.com/privacy/",
                  "stats_url": "https://mapxpet.com/api/v1/membership/stats.json"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/memberships": {
      "post": {
        "tags": [
          "Membership"
        ],
        "operationId": "createMembership",
        "summary": "Start a membership signup for a human (no key). Returns a Stripe Checkout URL the human must complete.",
        "description": "Only call when your human asked for a membership. Nothing is charged and nothing activates until the human pays on the returned Stripe Checkout page. Store member_token securely; it is shown once. Rate-limited per IP and per email. Step-by-step agent recipe: see the 'How to sign up for MapX Pet membership (for AI agents)' section of https://mapxpet.com/llms.txt and https://mapxpet.com/membership/. Ask your human before calling; hand them checkout_url; never enter card details.",
        "security": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Repeat-safe key; same key within 24h returns the same membership."
          },
          {
            "name": "User-Agent",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Please name your agent, e.g. 'Grok-Agent/1.0 (+https://x.ai)'. Logged only."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MembershipCreateRequest"
              },
              "example": {
                "plan": "monthly",
                "human": {
                  "email": "jane@example.ca",
                  "name": "Jane Doe"
                },
                "agent": {
                  "name": "Grok",
                  "vendor": "xAI"
                },
                "payment": {
                  "method": "checkout"
                },
                "locale": "en-CA",
                "consent": {
                  "human_requested": true
                },
                "pets": [
                  {
                    "type": "dog",
                    "name": "Maple"
                  },
                  {
                    "type": "dog"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending membership created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipCreated"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; wait retry_after seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/memberships/{id}": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "getMembership",
        "summary": "Membership status, entitlements and codes (codes only when active)",
        "security": [
          {
            "memberToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^mem_[0-9A-Z]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Membership",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Membership"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; wait retry_after seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Poll every poll_after_seconds (30 s) for up to 15 min, then occasionally, until status is active."
      }
    },
    "/api/v1/memberships/{id}/cancel": {
      "post": {
        "tags": [
          "Membership"
        ],
        "operationId": "cancelMembership",
        "summary": "Cancel at end of the paid period (access continues until then)",
        "security": [
          {
            "memberToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^mem_[0-9A-Z]{16}$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancellation scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Membership"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; wait retry_after seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/coupons/{code}": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "checkPartnerCode",
        "summary": "For partners: is a member code currently valid? Returns no personal data.",
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^MXP-[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Validity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "valid"
                  ],
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "offers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PartnerOffer"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; wait retry_after seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/memberships/adfree/{code}": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "claimAdFree",
        "summary": "Browser link (for the human, not agents): turns on ad-free browsing in this browser, then redirects to the homepage",
        "security": [],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^AF[A-HJ-NP-Z2-9]{8}$"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Sets first-party cookie mxp_adfree (signed pass, expires shortly after the paid period) and redirects to /",
            "headers": {
              "Set-Cookie": {
                "schema": {
                  "type": "string"
                }
              },
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown, used or expired code",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/membership/stats.json": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "getMembershipStats",
        "summary": "Public member count (no auth; cached about 5 minutes)",
        "security": [],
        "responses": {
          "200": {
            "description": "Count",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipStats"
                },
                "example": {
                  "members_active": 0,
                  "pets_helped": 0,
                  "counter_threshold": 6,
                  "updated_at": "2026-11-01T14:45:00Z"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/memberships/{id}/pets": {
      "put": {
        "tags": [
          "Membership"
        ],
        "operationId": "replaceMembershipPets",
        "summary": "Replace the member's pet list (0-10 pets)",
        "security": [
          {
            "memberToken": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^mem_[0-9A-Z]{16}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pets"
                ],
                "additionalProperties": false,
                "properties": {
                  "pets": {
                    "$ref": "#/components/schemas/Pets"
                  }
                }
              },
              "example": {
                "pets": [
                  {
                    "type": "dog",
                    "name": "Maple"
                  },
                  {
                    "type": "cat"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated membership",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Membership"
                }
              }
            }
          },
          "400": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/membership/checkout": {
      "post": {
        "tags": [
          "Membership"
        ],
        "operationId": "startMembershipCheckout",
        "summary": "Start a paid membership (Stripe Checkout). Implemented on the preview backend (Stripe test mode).",
        "description": "Creates a Stripe Checkout Session (subscription, C$3.99/month + tax via Stripe Tax, billing address required) and a pending member. Returns checkout_url, which a human must open to pay. Agents cannot pay. Agent signups are tagged \"via AI assistant\". Rate limit: 10/hour/IP.",
        "parameters": [
          {
            "name": "MapXPet-Agent",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional agent name/version; marks the signup as agent-initiated."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "city": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "agent_name": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "web",
                      "agent"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkout started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "checkout_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "checkout_session_id": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending"
                      ]
                    },
                    "status_token": {
                      "type": "string"
                    },
                    "status_url": {
                      "type": "string"
                    },
                    "price": {
                      "type": "object"
                    },
                    "human_action_required": {
                      "type": "string"
                    },
                    "signup_via": {
                      "type": "string",
                      "enum": [
                        "via AI assistant"
                      ]
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "already_member"
          },
          "422": {
            "description": "invalid_email"
          },
          "429": {
            "description": "rate_limited"
          },
          "502": {
            "description": "payments_error"
          },
          "503": {
            "description": "payments_unavailable (use fallback /api/membership/signup)"
          }
        }
      }
    },
    "/api/membership/status": {
      "get": {
        "tags": [
          "Membership"
        ],
        "operationId": "getMembershipCheckoutStatus",
        "summary": "Check activation of one signup by its status_token (preview backend).",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^mxp_st_"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status of this signup only",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active",
                        "past_due",
                        "cancelled"
                      ]
                    },
                    "activated_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "plan": {
                      "type": "object"
                    },
                    "cancel_at_period_end": {
                      "type": "boolean"
                    },
                    "current_period_end": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown token"
          },
          "429": {
            "description": "rate_limited"
          }
        }
      }
    },
    "/api/membership/portal": {
      "post": {
        "tags": [
          "Membership"
        ],
        "operationId": "openBillingPortal",
        "summary": "Get a Stripe Customer Portal link for the caller's own membership (preview backend, Stripe test mode).",
        "description": "Authenticate with the status_token from checkout (body \"token\") or the member token (Authorization: Bearer). Returns a short-lived, single-use portal_url for that member's own Stripe customer only. An agent may request the link, but the human must open it to cancel (at period end, with an optional reason), update the card, billing address or email, or view invoices. No plan changes or pausing. Rate limit: 20/hour/IP. JSON requests get JSON; a plain form POST gets a 303 redirect.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "pattern": "^mxp_st_"
                  },
                  "return": {
                    "type": "string",
                    "enum": [
                      "/membership/",
                      "/account/"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Portal link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "portal_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "human_action_required": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "303": {
            "description": "Redirect to the portal (form POST)"
          },
          "401": {
            "description": "unauthorized"
          },
          "404": {
            "description": "no_billing_account"
          },
          "429": {
            "description": "rate_limited"
          },
          "502": {
            "description": "payments_error"
          },
          "503": {
            "description": "payments_unavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "memberToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mxp_mt_<base64url>",
        "description": "Per-membership token returned once by POST /api/v1/memberships. Grants read + cancel for that membership only."
      }
    },
    "schemas": {
      "Plan": {
        "type": "object",
        "required": [
          "id",
          "price",
          "currency",
          "interval"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ]
          },
          "price": {
            "type": "number",
            "examples": [
              3.99
            ]
          },
          "currency": {
            "type": "string",
            "const": "CAD"
          },
          "interval": {
            "type": "string",
            "enum": [
              "month",
              "year"
            ]
          },
          "price_is_placeholder": {
            "type": "boolean"
          },
          "tax": {
            "type": "string",
            "description": "GST/HST/QST added at checkout by the member's province."
          }
        }
      },
      "MembershipCreateRequest": {
        "type": "object",
        "required": [
          "plan",
          "human",
          "agent",
          "payment",
          "consent"
        ],
        "additionalProperties": false,
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ],
            "description": "annual only if offered (see plans.json)"
          },
          "human": {
            "type": "object",
            "required": [
              "email"
            ],
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "name": {
                "type": "string",
                "maxLength": 100
              }
            }
          },
          "agent": {
            "type": "object",
            "required": [
              "name",
              "vendor"
            ],
            "properties": {
              "name": {
                "type": "string",
                "maxLength": 60
              },
              "vendor": {
                "type": "string",
                "maxLength": 60
              },
              "version": {
                "type": "string",
                "maxLength": 40
              },
              "contact_url": {
                "type": "string",
                "format": "uri"
              }
            },
            "description": "Self-declared; logged, never used for authorization. The User-Agent header is also recorded."
          },
          "payment": {
            "type": "object",
            "required": [
              "method"
            ],
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "checkout"
                ],
                "description": "v1 supports only 'checkout' (Stripe Checkout, subscription mode). 'acp_spt' and 'x402' are reserved for future versions."
              }
            }
          },
          "locale": {
            "type": "string",
            "enum": [
              "en-CA",
              "fr-CA"
            ],
            "default": "en-CA"
          },
          "consent": {
            "type": "object",
            "required": [
              "human_requested"
            ],
            "properties": {
              "human_requested": {
                "type": "boolean",
                "const": true
              }
            }
          },
          "pets": {
            "$ref": "#/components/schemas/Pets"
          },
          "city": {
            "type": "string",
            "pattern": "^([a-z]{2}/[a-z0-9-]+|other)$",
            "description": "Optional preferred city slug (e.g. on/toronto). Sets the app default zoom and local event alerts area; changeable anytime.",
            "examples": [
              "on/toronto"
            ]
          }
        }
      },
      "NextAction": {
        "type": "object",
        "required": [
          "type",
          "url"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "human_checkout"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "instructions": {
            "type": "string"
          }
        }
      },
      "Entitlement": {
        "type": "object",
        "required": [
          "id",
          "description"
        ],
        "description": "Listed in fixed order: the three core benefits first (digital_content, shop_discount, partner_coupons), then event_alerts, then breeding_board and pets_for_sale; ad_free last as a supporting perk.",
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "digital_content",
              "shop_discount",
              "partner_coupons",
              "event_alerts",
              "breeding_board",
              "pets_for_sale",
              "ad_free"
            ]
          },
          "core": {
            "type": "boolean",
            "description": "true for the three core benefits"
          },
          "supporting": {
            "type": "boolean",
            "description": "true for ad_free (secondary perk)"
          },
          "description": {
            "type": "string"
          },
          "how_to_use": {
            "type": "string"
          }
        }
      },
      "PartnerOffer": {
        "type": "object",
        "properties": {
          "partner_listing_id": {
            "type": "string",
            "description": "Matches /api/v1/listings/{id}.json"
          },
          "partner_name": {
            "type": "string"
          },
          "offer": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "valid_until": {
            "type": "string",
            "format": "date"
          },
          "terms": {
            "type": "string"
          }
        }
      },
      "MemberCodes": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "shop_discount_code": {
            "type": "string"
          },
          "digital_content_code": {
            "type": "string"
          },
          "partner_member_code": {
            "type": "string"
          },
          "partner_offers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerOffer"
            }
          }
        }
      },
      "Membership": {
        "type": "object",
        "required": [
          "membership_id",
          "status"
        ],
        "properties": {
          "membership_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_payment",
              "active",
              "past_due",
              "canceled",
              "expired"
            ]
          },
          "plan": {
            "$ref": "#/components/schemas/Plan"
          },
          "current_period_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_at_period_end": {
            "type": "boolean"
          },
          "access_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "entitlements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Entitlement"
            }
          },
          "codes": {
            "$ref": "#/components/schemas/MemberCodes"
          },
          "next_action": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NextAction"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present while pending_payment/expired"
          },
          "manage_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Stripe Customer Portal link for the human"
          },
          "agent": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "vendor": {
                "type": "string"
              }
            }
          },
          "ad_free_activation_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Present when active. Single-use link (7 days) the human opens in a browser to turn on ad-free browsing for that browser. Not needed by agents; the API has no ads."
          },
          "checkout_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Stripe Checkout link for the human (copy of next_action.url). Present while pending_payment or expired (fresh link)."
          },
          "checkout_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When checkout_url expires (24 h)."
          },
          "pets": {
            "$ref": "#/components/schemas/Pets"
          },
          "pet_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          }
        }
      },
      "MembershipCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Membership"
          },
          {
            "type": "object",
            "required": [
              "member_token",
              "checkout_url",
              "checkout_expires_at",
              "next_action",
              "status_url"
            ],
            "properties": {
              "member_token": {
                "type": "string",
                "description": "Shown once. Use as Bearer token."
              },
              "member_token_note": {
                "type": "string"
              },
              "status_url": {
                "type": "string",
                "format": "uri"
              },
              "poll_after_seconds": {
                "type": "integer"
              },
              "terms_url": {
                "type": "string",
                "format": "uri"
              },
              "status": {
                "const": "pending_payment"
              }
            }
          }
        ]
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "bad_request",
                  "invalid_token",
                  "not_found",
                  "already_active",
                  "unsupported_payment_method",
                  "invalid_email",
                  "consent_required",
                  "invalid_pets",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "retry_after": {
                "type": "integer",
                "minimum": 0,
                "description": "Seconds to wait before retrying; present on 429 and 5xx (matches Retry-After header)."
              }
            }
          }
        }
      },
      "MembershipStats": {
        "type": "object",
        "required": [
          "members_active",
          "pets_helped",
          "counter_threshold",
          "updated_at"
        ],
        "properties": {
          "members_active": {
            "type": "integer",
            "minimum": 0,
            "description": "Real count of memberships with status active or past_due. Never inflated."
          },
          "pets_helped": {
            "type": "integer",
            "minimum": 0,
            "description": "Sum of registered pets (pet_count) across those same active/past_due memberships. Members with no pets registered add 0. Drops when a membership ends."
          },
          "counter_threshold": {
            "type": "integer",
            "minimum": 1,
            "description": "Display hint for the /membership/ page: the public counter (pets_helped) is shown only when members_active >= counter_threshold (currently 6); below it the page shows a 'Founding members wanted' line. Agents may still read and report the real numbers."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Pet": {
        "type": "object",
        "required": [
          "type"
        ],
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "dog",
              "cat",
              "bird",
              "rabbit",
              "small_animal",
              "reptile",
              "fish",
              "horse",
              "other"
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 40,
            "description": "Optional. Only stored if given; never shown publicly."
          }
        }
      },
      "Pets": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/Pet"
        },
        "minItems": 0,
        "maxItems": 10,
        "description": "Optional list of the member's pets (0-10). Each pet counts once toward the public 'pets helped' counter while the membership is active."
      }
    }
  },
  "servers": [
    {
      "url": "https://mapxpet.com",
      "description": "Production (NOT LIVE YET: endpoints return 404 until launch)"
    },
    {
      "url": "http://localhost:8787",
      "description": "Preview backend (Stripe test mode) for /api/membership/checkout and /api/membership/status"
    }
  ],
  "externalDocs": {
    "description": "MapXPet API for AI agents (human-readable guide; API not live yet)",
    "url": "https://mapxpet.com/for-agents/"
  }
}