{
  "openapi": "3.1.0",
  "info": {
    "title": "PPP API",
    "version": "1.0.0",
    "description": "Unified JSON API for applicant and clinic mobile apps. All routes require approved developer headers X-PPP-Dev-Id and X-PPP-License-Key. End-user routes also use Bearer tokens after login."
  },
  "servers": [
    {
      "url": "https://pawsitivityproject.org",
      "description": "Production (.org)"
    },
    {
      "url": "https://pawsitivityproject.com",
      "description": "Production (.com)"
    }
  ],
  "security": [
    {
      "DevId": [],
      "DevLicenseKey": []
    }
  ],
  "tags": [
    {
      "name": "Applicant — Public",
      "description": "Applicant: No access token required"
    },
    {
      "name": "Applicant — Auth",
      "description": "Applicant: Account and tokens"
    },
    {
      "name": "Applicant — Application",
      "description": "Applicant: Requires Bearer access token"
    },
    {
      "name": "Clinic — Public",
      "description": "Clinic: No access token required"
    },
    {
      "name": "Clinic — Auth",
      "description": "Clinic: Account and tokens"
    },
    {
      "name": "Clinic — Clinic",
      "description": "Clinic: Requires Bearer access token (vet user)"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Applicant",
      "tags": [
        "Applicant — Public",
        "Applicant — Auth",
        "Applicant — Application"
      ]
    },
    {
      "name": "Clinic",
      "tags": [
        "Clinic — Public",
        "Clinic — Auth",
        "Clinic — Clinic"
      ]
    }
  ],
  "components": {
    "securitySchemes": {
      "DevId": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PPP-Dev-Id",
        "description": "Developer ID (staff-approved registration)"
      },
      "DevLicenseKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PPP-License-Key",
        "description": "License key issued on approval"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque"
      }
    },
    "schemas": {
      "Applicant_SuccessEnvelope": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "data": {}
        },
        "required": [
          "ok",
          "data"
        ]
      },
      "Applicant_ErrorEnvelope": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "ok",
          "error"
        ]
      },
      "Applicant_RegisterRequest": {
        "type": "object",
        "required": [
          "email",
          "password",
          "first_name",
          "last_name"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "minLength": 10
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "Applicant_LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "Applicant_RefreshRequest": {
        "type": "object",
        "required": [
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "Applicant_ApplicationStep1Request": {
        "type": "object",
        "required": [
          "household_size",
          "annual_household_income",
          "assistance_narrative"
        ],
        "properties": {
          "household_size": {
            "type": "integer",
            "minimum": 1
          },
          "annual_household_income": {
            "type": "number",
            "minimum": 0
          },
          "assistance_narrative": {
            "type": "string",
            "minLength": 40,
            "maxLength": 8000
          }
        }
      },
      "Applicant_HouseholdRequest": {
        "type": "object",
        "required": [
          "members"
        ],
        "properties": {
          "members": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "full_name": {
                  "type": "string"
                },
                "age_years": {
                  "type": "integer",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "Applicant_LineRequest": {
        "type": "object",
        "required": [
          "pet_id",
          "vet_clinic_id",
          "need_category",
          "need_detail",
          "estimated_cost"
        ],
        "properties": {
          "line_id": {
            "type": "integer",
            "description": "Omit for create; set to update"
          },
          "pet_id": {
            "type": "integer"
          },
          "vet_clinic_id": {
            "type": "integer"
          },
          "need_category": {
            "type": "string",
            "enum": [
              "exam",
              "vaccines",
              "diagnostics",
              "surgery",
              "medications",
              "dental",
              "emergency",
              "other"
            ]
          },
          "need_detail": {
            "type": "string"
          },
          "need_other": {
            "type": "string",
            "description": "When need_category is other"
          },
          "estimated_cost": {
            "type": "number"
          }
        }
      },
      "Clinic_LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "Clinic_RefreshRequest": {
        "type": "object",
        "required": [
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "Clinic_LineOnBehalfRequest": {
        "type": "object",
        "required": [
          "applicant_email",
          "pet_id",
          "need_detail"
        ],
        "properties": {
          "applicant_email": {
            "type": "string",
            "format": "email"
          },
          "pet_id": {
            "type": "integer"
          },
          "need_category": {
            "type": "string"
          },
          "need_detail": {
            "type": "string"
          },
          "estimated_cost": {
            "type": "number"
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/applicant/config": {
      "get": {
        "tags": [
          "Applicant — Public"
        ],
        "summary": "Public app configuration",
        "responses": {
          "200": {
            "description": "Registration flags, program year, FPL percent, need categories"
          }
        }
      }
    },
    "/api/v1/applicant/openapi.json": {
      "get": {
        "tags": [
          "Applicant — Public"
        ],
        "summary": "This OpenAPI document",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON"
          }
        }
      }
    },
    "/api/v1/applicant/register": {
      "post": {
        "tags": [
          "Applicant — Auth"
        ],
        "summary": "Create applicant account",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_RegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created; verify email via link in inbox"
          },
          "403": {
            "description": "registration_closed"
          },
          "409": {
            "description": "email_taken"
          },
          "422": {
            "description": "validation_error"
          }
        }
      }
    },
    "/api/v1/applicant/login": {
      "post": {
        "tags": [
          "Applicant — Auth"
        ],
        "summary": "Sign in and receive tokens",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "tokens + user"
          },
          "401": {
            "description": "invalid_credentials"
          }
        }
      }
    },
    "/api/v1/applicant/token/refresh": {
      "post": {
        "tags": [
          "Applicant — Auth"
        ],
        "summary": "Exchange refresh token for new token pair",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_RefreshRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "tokens + user"
          },
          "401": {
            "description": "invalid_refresh"
          }
        }
      }
    },
    "/api/v1/applicant/logout": {
      "post": {
        "tags": [
          "Applicant — Auth"
        ],
        "summary": "Revoke refresh token",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_RefreshRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed out"
          }
        }
      }
    },
    "/api/v1/applicant/forgot-password": {
      "post": {
        "tags": [
          "Applicant — Auth"
        ],
        "summary": "Email password reset link (web URL)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generic success message"
          }
        }
      }
    },
    "/api/v1/applicant/me": {
      "get": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Current user profile",
        "responses": {
          "200": {
            "description": "user object"
          },
          "401": {
            "description": "unauthorized"
          }
        }
      }
    },
    "/api/v1/applicant/dashboard": {
      "get": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Dashboard summary for current program year",
        "responses": {
          "200": {
            "description": "program_year, application summary, user"
          }
        }
      }
    },
    "/api/v1/applicant/application": {
      "get": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Full application state (all steps)",
        "responses": {
          "200": {
            "description": "application, household_members, pets with line_items, locks"
          }
        }
      },
      "put": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Save step 1 — income, FPL, narrative",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_ApplicationStep1Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated application"
          },
          "409": {
            "description": "locked"
          },
          "422": {
            "description": "validation_error"
          }
        }
      },
      "patch": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Save step 1 (alias of PUT)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_ApplicationStep1Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated application"
          }
        }
      }
    },
    "/api/v1/applicant/application/submit": {
      "post": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Submit application for staff review",
        "responses": {
          "200": {
            "description": "message + application"
          },
          "403": {
            "description": "email_not_verified"
          },
          "422": {
            "description": "Missing pets, lines, or narrative"
          }
        }
      }
    },
    "/api/v1/applicant/household": {
      "put": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Replace household member list (step 2)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_HouseholdRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Full application state"
          }
        }
      }
    },
    "/api/v1/applicant/pets": {
      "post": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Create or update pet (step 3)",
        "description": "Send application/json OR multipart/form-data with optional photo file field named photo.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "species"
                ],
                "properties": {
                  "pet_id": {
                    "type": "integer"
                  },
                  "name": {
                    "type": "string"
                  },
                  "species": {
                    "type": "string"
                  },
                  "age_years": {
                    "type": "number"
                  },
                  "weight_lbs": {
                    "type": "number"
                  },
                  "sex": {
                    "type": "string",
                    "enum": [
                      "unknown",
                      "female",
                      "male"
                    ]
                  },
                  "microchip": {
                    "type": "string"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "pet_id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "species": {
                    "type": "string"
                  },
                  "photo": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Full application state"
          }
        }
      }
    },
    "/api/v1/applicant/lines": {
      "post": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Create or update cost line (step 4)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Applicant_LineRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Full application state"
          }
        }
      }
    },
    "/api/v1/applicant/fpl-check": {
      "get": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Live federal poverty guideline check",
        "parameters": [
          {
            "name": "household_size",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "annual_household_income",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "FPL evaluation for current program year"
          }
        }
      }
    },
    "/api/v1/applicant/vet-clinics": {
      "get": {
        "tags": [
          "Applicant — Application"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Approved clinics for line items",
        "responses": {
          "200": {
            "description": "clinics array"
          }
        }
      }
    },
    "/api/v1/clinic/config": {
      "get": {
        "tags": [
          "Clinic — Public"
        ],
        "summary": "Public configuration (registration flag, need categories)",
        "responses": {
          "200": {
            "description": "ok envelope with registration and categories"
          }
        }
      }
    },
    "/api/v1/clinic/openapi.json": {
      "get": {
        "tags": [
          "Clinic — Public"
        ],
        "summary": "OpenAPI document",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON"
          }
        }
      }
    },
    "/api/v1/clinic/register": {
      "post": {
        "tags": [
          "Clinic — Auth"
        ],
        "summary": "Register clinic account (multipart: fields + w9 file)",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password",
                  "first_name",
                  "last_name",
                  "legal_name",
                  "address_line",
                  "city",
                  "region",
                  "postal_code",
                  "clinic_phone",
                  "license_number",
                  "contact_person",
                  "w9"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 10
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "legal_name": {
                    "type": "string"
                  },
                  "address_line": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "region": {
                    "type": "string"
                  },
                  "postal_code": {
                    "type": "string"
                  },
                  "clinic_phone": {
                    "type": "string"
                  },
                  "license_number": {
                    "type": "string"
                  },
                  "contact_person": {
                    "type": "string"
                  },
                  "w9": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "clinic_id and user_id"
          },
          "403": {
            "description": "registration_closed"
          },
          "409": {
            "description": "email_taken"
          },
          "422": {
            "description": "validation_error"
          }
        }
      }
    },
    "/api/v1/clinic/login": {
      "post": {
        "tags": [
          "Clinic — Auth"
        ],
        "summary": "Sign in",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Clinic_LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "tokens, user, clinic"
          },
          "401": {
            "description": "invalid_credentials"
          }
        }
      }
    },
    "/api/v1/clinic/token/refresh": {
      "post": {
        "tags": [
          "Clinic — Auth"
        ],
        "summary": "Refresh access token",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Clinic_RefreshRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "tokens + user"
          }
        }
      }
    },
    "/api/v1/clinic/logout": {
      "post": {
        "tags": [
          "Clinic — Auth"
        ],
        "summary": "Revoke refresh token",
        "responses": {
          "200": {
            "description": "Signed out"
          }
        }
      }
    },
    "/api/v1/clinic/forgot-password": {
      "post": {
        "tags": [
          "Clinic — Auth"
        ],
        "summary": "Send password reset email (web link)",
        "responses": {
          "200": {
            "description": "Generic message"
          }
        }
      }
    },
    "/api/v1/clinic/me": {
      "get": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Current user and clinic profile",
        "responses": {
          "200": {
            "description": "user + clinic"
          }
        }
      }
    },
    "/api/v1/clinic/dashboard": {
      "get": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Dashboard lines grouped by status, paid filter, applicant lookup",
        "parameters": [
          {
            "name": "mailed_start",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "mailed_end",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "lookup",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "description": "Applicant email for pet lookup"
          }
        ],
        "responses": {
          "200": {
            "description": "clinic, lines, mailed_filter, lookup"
          }
        }
      }
    },
    "/api/v1/clinic/profile": {
      "post": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Update clinic profile (optional multipart w9)",
        "responses": {
          "200": {
            "description": "Updated me payload"
          }
        }
      },
      "put": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Update clinic profile",
        "responses": {
          "200": {
            "description": "Updated me payload"
          }
        }
      }
    },
    "/api/v1/clinic/lines": {
      "post": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Create need line on behalf of applicant",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Clinic_LineOnBehalfRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "line_item_id"
          },
          "403": {
            "description": "clinic_not_approved"
          }
        }
      }
    },
    "/api/v1/clinic/lines/fulfill": {
      "post": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Upload bill and mark line fulfilled",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "line_id",
                  "bill"
                ],
                "properties": {
                  "line_id": {
                    "type": "integer"
                  },
                  "bill": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success message"
          }
        }
      }
    },
    "/api/v1/clinic/lines/bill": {
      "get": {
        "tags": [
          "Clinic — Clinic"
        ],
        "security": [
          {
            "DevId": [],
            "DevLicenseKey": [],
            "BearerAuth": []
          }
        ],
        "summary": "Download bill file for a line",
        "parameters": [
          {
            "name": "line_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Binary PDF or image"
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    }
  }
}
