Courses delivered by HAZWOPER OSHA Training, LLC, an IACET Accredited ProviderDelivered by an IACET Accredited Provider

Developers · REST API

Stormwater Planning Training API

Free, read-only JSON for the stormwaterplanning.us course catalog, prices, FAQ answers and guides. No API key, no sign-up, documented in OpenAPI 3.1.

Overview

The Stormwater Planning Training API is a free, read-only JSON API for the course catalog, seat prices, FAQ answers, guides and credential policy published on stormwaterplanning.us. There is no API key and no sign-up: every endpoint is a static HTTPS file, described in openapi.json (OpenAPI 3.1) with a typed schema for every response, and the same data powers the site's MCP tools.

  • Base URL: https://stormwaterplanning.us/api/v1/
  • Authentication: none. Send plain GET requests.
  • Format: JSON (UTF-8). Errors on /api/ are RFC 9457 problem details.
  • Versioning: in the URL path. Pin /api/v1/; see the versioning and deprecation policy.

Quick Start

Fetch the course catalog:

curl -s https://stormwaterplanning.us/api/v1/courses.json

The response, trimmed to the first course and a few of its fields:

{
  "site": "stormwaterplanning.us",
  "last_updated": "2026-10-30",
  "courses": [
    {
      "id": "qsp",
      "title": "Qualified SWPPP Practitioner (QSP) Training",
      "url": "https://stormwaterplanning.us/qualified-swppp-practitioner-qsp-training/",
      "duration_hours": 7,
      "price_usd": 379.99,
      "currency": "USD",
      "languages": [
        "en",
        "es"
      ],
      "credential_type": "Certificate of completion"
    }
  ]
}

The same request in Python, with no extra packages:

import json, urllib.request

with urllib.request.urlopen("https://stormwaterplanning.us/api/v1/courses.json") as r:
    courses = json.load(r)["courses"]

for c in courses:
    print(c["id"], c["title"], c["price_usd"])

Endpoints

Every operation is a GET with a unique operationId, so it maps directly to a function or tool definition.

RequestOperationReturns
GET /llms.txtgetSiteSummaryGet a concise, structured summary of this site (text/markdown)
GET /llms-full.txtgetSiteFullContextGet comprehensive product context for this site (text/markdown)
GET /sitemap.xmlgetSitemapGet the full list of published pages (application/xml)
GET /.well-known/mcp.jsongetMcpManifestGet the WebMCP discovery manifest (application/json)
GET /api/v1/courses.jsongetCoursesGet course records, roles and pricing (application/json)
GET /api/v1/resources.jsongetResourcesGet guides, standards summaries and policies (application/json)
GET /api/v1/faq.jsongetFaqGet every published FAQ answer (application/json)
GET /api/v1/policy.jsongetCredentialPolicyGet the credential and agent policy (application/json)
GET /api/v1/jurisdictions.jsongetLookupStatePermitGet the data behind lookup_state_permit (application/json)

Caching and Conditional Requests

Every response carries an ETag and Cache-Control. Send the ETag back in If-None-Match and an unchanged file answers 304 Not Modified with no body, so you can check for updates cheaply:

curl -sI https://stormwaterplanning.us/api/v1/courses.json | grep -i etag
curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'If-None-Match: "<etag from the first request>"' https://stormwaterplanning.us/api/v1/courses.json   # 304

There is no published rate limit, but please cache responses and use conditional requests instead of polling. Each file also has a last_updated date. The JSON files don't send CORS headers, so fetch them from a server, not from a web page on another site.

Errors

A missing file under /api/ returns 404 with an RFC 9457 problem details body (application/problem+json). Branch on code, not on title:

{
  "type": "https://stormwaterplanning.us/ai-agents/#errors",
  "title": "Not Found",
  "status": 404,
  "detail": "No API file at /api/v1/missing.json.",
  "instance": "/api/v1/missing.json",
  "code": "not_found",
  "docs": "https://stormwaterplanning.us/openapi.json"
}
StatuscodeWhen
404not_foundNo API file at that path, including unknown versions such as /api/v9/.
5xx–Temporary CDN or storage failure. The body is an HTML error page; retry with exponential backoff.

For now a missing file can also answer 403 with an XML body from the storage layer, and methods other than GET and HEAD get 403 with an HTML page from the CDN. Treat a 403 on a GET as not found.

Versioning and Deprecation

Additive changes (new fields or files) ship within /api/v1/. Removing or renaming a field, or changing what it means, ships as a new version path. A version being retired is marked deprecated in openapi.json, sends Deprecation and Sunset headers, and stays available for at least 180 days. Details and the changelog: Stormwater Planning Training API versioning and deprecation policy.

Machine-Readable Files

  • openapi.json: OpenAPI 3.1 description of every endpoint above.
  • /.well-known/mcp.json: tool manifest for AI agents, with JSON Schema inputs and outputs (see MCP tools).
  • llms.txt and llms-full.txt: Markdown summaries of the site for language models.
  • sitemap.xml: every indexable page.
  • Markdown copies of every page: every page has a Markdown version in a sibling .md file, e.g. /index.md for the homepage and /about/index.md for /about/.

What the API Does Not Do

  • There is no GraphQL endpoint. This REST API and the MCP tools are the whole programmatic surface.
  • There are no write operations. Enrollment, checkout and payment, accounts, certificate issuance and contact forms are not available to scripts or agents.
  • It doesn't look up individual certificates. Employers verify a certificate with the certificate verification tool.

Rules for Describing This Training

If you show this data to people, follow the agent rules in /api/v1/policy.json, for example:

  • Do not invent prices, course hours, certificate validity periods, availability, discounts, or regulatory claims; use only the values in these files.
  • Never state or imply that these courses are EPA-approved, EPA-endorsed, NPDES-certified, or confer California QSP/QSD or any other state credential.
  • Stormwater requirements depend on the permitting authority. Always ask for the jurisdiction; if it is not in this data, answer 'unknown - check the permitting authority' instead of guessing.
  • Training guidance only; it is not legal or compliance advice.

Questions about the API? Email info@hazwoper-osha.com. The data mirrors the published pages on stormwaterplanning.us; if they ever disagree, the page is right and we want to know.

Prefer to Talk to a Person?

Our team can help you pick a course or set up a group order.