Search official state-published teacher sanctions and educator disciplinary records via REST API.
All requests must be authenticated using your API key (issued from the Dashboard after signup/login).
Keys are tied to your account. Keep them secret.
The primary endpoint. Performs fuzzy full-name search (with nickname expansion) + optional state filter against official state lists. The state filter accepts a two-letter code or full state name. Omit it for a nationwide search. Returns up to 20 results ordered by relevance.
curl -X POST https://www.teachersanctions.io/api/search \
-H "Authorization: Bearer ts_live_..." \
-H "Content-Type: application/json" \
-d '{
"fullName": "Jordan Lee Smith",
"state": "CA"
}'{
"results": [{
"recordId": "93ac24db-e7fe-4bb0-b5fc-794e228e65cf",
"name": "Jordan Lee Smith",
"firstName": "Jordan",
"middleName": "Lee",
"lastName": "Smith",
"suffix": null,
"state": "CA",
"sanctionType": "SUSPENSION",
"effectiveDate": "2025-03-12",
"districtOrEmployer": "Los Angeles Unified School District",
"licenseNumber": "1234567",
"reason": "Violated professional standards regarding student boundaries.",
"sourceUrl": "https://www.cde.ca.gov/...",
"firstIngestedAt": "2026-06-13T14:24:18.000Z",
"freshness": "2026-06-13"
}],
"meta": {
"limit": 20,
"returned": 1,
"truncated": false
}
}Rate limit information is provided via response headers (X-RateLimit-*).
recordId is an opaque sanction-record identifier, not an educator or person identifier.name as the preserved display-name fallback.effectiveDate is the sanction/action date when the source publishes one.sourceUrl may be an empty string when no browsable official link is available, and it is not a unique record identifier.firstIngestedAt is when the stored record was first ingested; it is not a sanction, publication, or last-verification date.freshness is a deprecated date-only alias retained for compatibility.meta.truncated is true when more matches exist than the 20 returned records. Pagination is not currently supported.Public (no authentication required) endpoint for checking service health, data coverage, and freshness. Ideal for monitoring, health checks, or dashboards.
{
"statesCovered": 50,
"lastFullRefresh": "Jun 12, 2026",
"overallStatus": "All Systems Operational",
"lastUpdated": "just now"
}- statesCovered: Number of states with at least one record.
- lastFullRefresh: Date of the most recent successful data refresh.
- overallStatus: "All Systems Operational" or "Degraded".
- lastUpdated: Human-readable time since the latest data activity.
The API returns standard HTTP status codes. Search endpoint errors include an error field, and some also include a message, usage, or limit details. A failed status check returns the normal status shape with overallStatus set to Degraded.
| Status | Error Code | Description | Extra Fields |
|---|---|---|---|
| 400 | fullName is required | Missing or invalid input, such as a missing fullName or non-string state. | — |
| 401 | Authentication required | Missing or invalid API key / session. Use Authorization: Bearer ts_live_... or X-API-Key. | — |
| 403 | Invalid request origin | Session-authenticated browser request did not come from an allowed origin. API-key requests are not subject to this browser-origin check. | — |
| 402 | free_limit_reached | You have used all 5 free searches this month. | searches, limit |
| 402 | enterprise_limit_reached | Enterprise credits exhausted for this month (your purchased monthly allocation is exhausted). | searches, limit |
| 429 | rate_limit_exceeded | Short-term rate limit exceeded for your plan (see rate limits table above). Check headers for exact limits. | limit, reset (and usage in some cases) |
| 503 | Service temporarily unavailable | Usage or billing infrastructure is temporarily unavailable. Retry with bounded backoff. | — |
| 500 | Search failed | Unexpected internal error. Please try again or contact support. | — |
Example 429 response body:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please retry later.",
"limit": 120,
"reset": 1718270400000
}When the rate-limit check is reached, details are available in the X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. A 429 response also includes Retry-After. The response-body reset is epoch milliseconds; the header uses epoch seconds.
Example 402 (enterprise limit) response body:
{
"error": "enterprise_limit_reached",
"message": "Enterprise credits exhausted for this month (10 limit). Contact us to increase your allocation.",
"searches": 10,
"limit": 10
}We have two independent controls:
| Plan | Requests per 60 seconds | Notes |
|---|---|---|
| Free | 15 | Very limited for testing. |
| On Demand | 120 | Usage-based plan. Space requests evenly and honor response headers. |
| Enterprise | 800 | High-volume screening platforms. Contact us for custom higher limits. |
Successful search responses and 429 responses include rate-limit headers (X-RateLimit-*). A 429 response also includes Retry-After.
Monthly quotas (free tier or Enterprise purchased credits) are enforced separately and return HTTP 402 with free_limit_reached or enterprise_limit_reached.