{
  "openapi": "3.1.0",
  "info": {
    "title": "Hopeful Child school license",
    "version": "1.1.0",
    "description": "Buy Hopeful Child for a school without a sales call, then manage teacher seats. Price: $24 per teacher seat per month, billed annually ($288 per seat per year). Minimum 5 seats. Invoice billing (net 30) starts at 10 seats. The admin must be an adult (18+) who is authorized to buy for the school and who accepts the Terms. You must be 18 or older to create an account. Accounts are for parents, guardians and educators. Set age_confirmed, adult_confirmed, terms_accepted and autorenew_acknowledged only with that person's consent; missing or false age_confirmed returns 400 age_not_confirmed. The license renews automatically every year at $288 per teacher until cancelled online at https://billing.stripe.com/p/login/3cI5kx7Za7BH5QM5k7gYU00. Never collect student information.",
    "x-offer": {
      "product": "Hopeful Child School License",
      "price_per_seat_month_usd": 24,
      "billed": "annually",
      "price_per_seat_year_usd": 288,
      "min_seats": 5,
      "max_seats_card_checkout": 1000,
      "invoice_min_seats": 10,
      "invoice_terms": "net 30",
      "includes": [
        "print view",
        "class pack",
        "50 educator briefs a day per seat",
        "email sign-in",
        "API key per seat"
      ],
      "refunds": "https://hopefulchild.com/refunds",
      "terms": "https://hopefulchild.com/terms#school"
    },
    "contact": {
      "email": "hope@hopefulchild.com"
    }
  },
  "servers": [
    {
      "url": "https://hopefulchild.com"
    }
  ],
  "paths": {
    "/api/schools/checkout": {
      "post": {
        "operationId": "startSchoolCheckout",
        "summary": "Start a card checkout (Stripe Checkout) or create a net-30 invoice",
        "description": "Card: returns checkout_url; send the human admin there to pay. Invoice (10+ seats): Stripe emails the invoice to admin_email; hosted_invoice_url is returned when available (null if a tax-exempt certificate is under review). Then poll status_url. After payment the admin gets a manage link by email. Rate limit: 10 requests per hour per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutRequest"
              },
              "examples": {
                "card": {
                  "value": {
                    "seats": 5,
                    "billing": "card",
                    "admin_email": "principal@school.org",
                    "school_name": "Lincoln Middle School",
                    "age_confirmed": true,
                    "adult_confirmed": true,
                    "terms_accepted": true,
                    "autorenew_acknowledged": true
                  }
                },
                "invoice": {
                  "value": {
                    "seats": 12,
                    "billing": "invoice",
                    "admin_email": "office@district.org",
                    "school_name": "Lincoln Middle School",
                    "age_confirmed": true,
                    "adult_confirmed": true,
                    "terms_accepted": true,
                    "autorenew_acknowledged": true,
                    "invoice": {
                      "billing_name": "Lincoln Unified School District",
                      "po_number": "PO-1234",
                      "address": {
                        "line1": "1 Main St",
                        "city": "Springfield",
                        "state": "IL",
                        "postal_code": "62701",
                        "country": "US"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Card checkout created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardCheckout"
                }
              }
            }
          },
          "201": {
            "description": "Invoice created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceCheckout"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, seats_below_minimum, invoice_requires_10_seats, invalid_email, missing_school_name, age_not_confirmed, adult_not_confirmed, terms_not_accepted, autorenew_not_acknowledged, invoice_details_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "stripe_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/status": {
      "get": {
        "operationId": "getSchoolCheckoutStatus",
        "summary": "Read checkout or invoice status",
        "description": "Pass exactly one of checkout_session_id or invoice_id (from status_url). Rate limit: 60 per hour per IP.",
        "parameters": [
          {
            "name": "checkout_session_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "invoice_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status (emails masked)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/tax-certificate": {
      "post": {
        "operationId": "uploadTaxExemptCertificate",
        "summary": "Upload a tax-exempt certificate after an invoice checkout",
        "description": "PDF, PNG, or JPG up to 5 MB. Requires the certificate_upload_token returned by POST /api/schools/checkout when invoice.tax_exempt_certificate_pending is true, sent as Authorization: Bearer <token> (valid 2 hours, one use), or a signed-in school admin session. Rate limited per IP. The certificate goes to a person for review and the invoice stays a draft until then.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "tax_exempt_certificate_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/manage-link": {
      "post": {
        "operationId": "requestManageLink",
        "summary": "Email the school admin a one-time manage link (15 minutes)",
        "description": "Always returns the same 200 message, whether or not the email has an active school. Rate limits: 10 per hour per IP, 3 per 15 minutes per email.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Same message every time",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/seats": {
      "post": {
        "operationId": "addTeacherSeat",
        "summary": "Give a teacher a seat (emails them a sign-in link)",
        "security": [
          {
            "adminSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Teacher email (adults only)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "seatId": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "seats_full, already_added, or invalid email",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "sign_in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/seats/{seatId}": {
      "delete": {
        "operationId": "removeTeacherSeat",
        "summary": "Remove a teacher seat and revoke its API key",
        "security": [
          {
            "adminSession": []
          }
        ],
        "parameters": [
          {
            "name": "seatId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "sign_in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/seats/quantity": {
      "post": {
        "operationId": "increaseSeatQuantity",
        "summary": "Buy more seats (increase only)",
        "description": "To reduce seats, write hope@hopefulchild.com.",
        "security": [
          {
            "adminSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "seats"
                ],
                "properties": {
                  "seats": {
                    "type": "integer",
                    "description": "New total, at least the current total."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Change submitted to Stripe",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "pending": {
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "seats_decrease_not_supported",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "sign_in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "stripe_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/schools/proration-preview": {
      "get": {
        "operationId": "previewSeatChange",
        "summary": "Preview the prorated amount due for a new seat total",
        "security": [
          {
            "adminSession": []
          }
        ],
        "parameters": [
          {
            "name": "seats",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Amount due in USD",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "amount": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "sign_in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "stripe_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "adminSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "hc_school_admin",
        "description": "Browser session set when the admin opens the emailed manage link (POST /api/schools/manage-link). Not available to agents without the admin's browser. Lasts 12 hours."
      }
    },
    "schemas": {
      "CheckoutRequest": {
        "type": "object",
        "required": [
          "seats",
          "billing",
          "admin_email",
          "school_name",
          "age_confirmed",
          "adult_confirmed",
          "terms_accepted",
          "autorenew_acknowledged"
        ],
        "properties": {
          "seats": {
            "type": "integer",
            "minimum": 5,
            "description": "Teacher seats. 5 minimum; 10 minimum for invoice."
          },
          "billing": {
            "type": "string",
            "enum": [
              "card",
              "invoice"
            ]
          },
          "admin_email": {
            "type": "string",
            "format": "email"
          },
          "school_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "age_confirmed": {
            "const": true,
            "description": "The admin confirms: I am 18 or older and a parent, guardian or educator. You must be 18 or older to create an account. Accounts are for parents, guardians and educators. Missing or false returns 400 age_not_confirmed."
          },
          "adult_confirmed": {
            "const": true,
            "description": "The admin is authorized to buy for the school."
          },
          "terms_accepted": {
            "const": true,
            "description": "The admin accepts https://hopefulchild.com/terms"
          },
          "autorenew_acknowledged": {
            "const": true,
            "description": "The admin understands the School license renews automatically every year at $288 per teacher until they cancel. Cancel online anytime at https://billing.stripe.com/p/login/3cI5kx7Za7BH5QM5k7gYU00. Required for card and invoice billing; missing or false returns 400 autorenew_not_acknowledged."
          },
          "invoice": {
            "$ref": "#/components/schemas/InvoiceDetails"
          }
        }
      },
      "InvoiceDetails": {
        "type": "object",
        "description": "Required when billing is invoice.",
        "required": [
          "billing_name",
          "address"
        ],
        "properties": {
          "billing_name": {
            "type": "string"
          },
          "address": {
            "type": "object",
            "required": [
              "line1",
              "city",
              "state",
              "postal_code",
              "country"
            ],
            "properties": {
              "line1": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "state": {
                "type": "string"
              },
              "postal_code": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "po_number": {
            "type": "string"
          },
          "tax_id": {
            "type": "string",
            "description": "US EIN (12-3456789) is attached to the Stripe customer."
          },
          "tax_exempt_certificate_id": {
            "type": "string",
            "description": "Legacy. Use tax_exempt_certificate_pending instead."
          },
          "tax_exempt_certificate_pending": {
            "type": "boolean",
            "description": "Set true to hold the invoice for review and receive certificate_upload_token for POST /api/schools/tax-certificate."
          }
        }
      },
      "CardCheckout": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true
          },
          "billing": {
            "const": "card"
          },
          "checkout_url": {
            "type": "string",
            "format": "uri"
          },
          "checkout_session_id": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "status_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "InvoiceCheckout": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true
          },
          "billing": {
            "const": "invoice"
          },
          "invoice_id": {
            "type": "string"
          },
          "subscription_id": {
            "type": "string"
          },
          "invoice_status": {
            "type": "string",
            "enum": [
              "open",
              "draft_pending_review"
            ]
          },
          "hosted_invoice_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "ok": {
            "const": true
          },
          "status": {
            "type": "string",
            "description": "pending until paid, then the license status (for example active or past_due)."
          },
          "seats_total": {
            "type": "integer"
          },
          "seats_used": {
            "type": "integer"
          },
          "period_end": {
            "type": "integer",
            "description": "Unix seconds; 0 while pending."
          },
          "admin_email_masked": {
            "type": "string"
          },
          "manage_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "code",
          "error"
        ],
        "properties": {
          "ok": {
            "const": false
          },
          "code": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        }
      }
    }
  },
  "security": [
    {}
  ]
}