Error Handling
Error Format
Section titled “Error Format”All API errors follow the RFC 7807 Problem Details format. Every error response is a JSON object with a consistent structure, regardless of the HTTP status code:
{ "type": "https://api.studyplug.org/errors/not-found", "title": "Not Found", "status": 404, "detail": "No skill found with slug 'add-within-99'.", "instance": "/api/v1/skills/add-within-99"}Field Reference
Section titled “Field Reference”| Field | Type | Description |
|---|---|---|
type | string | A URI identifying the error category. Stable across API versions. |
title | string | A short, human-readable summary of the error type. |
status | integer | The HTTP status code. |
detail | string | A human-readable explanation specific to this occurrence. |
instance | string | The request path that produced the error. |
errors | array | Present on validation errors. Lists individual field-level issues. |
retryAfter | integer | Present on rate-limit errors. Seconds until the client can retry. |
Error Types
Section titled “Error Types”Validation Error (400)
Section titled “Validation Error (400)”Returned when the request body or query parameters fail schema validation.
{ "type": "https://api.studyplug.org/errors/bad-request", "title": "Bad Request", "status": 400, "detail": "Request body failed validation.", "instance": "/api/v1/generate", "errors": [ { "field": "count", "message": "Number must be greater than or equal to 1", "value": 0 }, { "field": "grade", "message": "Required" } ]}The errors array provides field-level detail so you can highlight specific form inputs or log exact issues.
Not Found (404)
Section titled “Not Found (404)”Returned when a referenced resource does not exist.
{ "type": "https://api.studyplug.org/errors/not-found", "title": "Not Found", "status": 404, "detail": "No skill found with slug 'add-within-99'.", "instance": "/api/v1/skills/add-within-99"}Generation Failed (422)
Section titled “Generation Failed (422)”Returned when the request is syntactically valid but a parameter value is out of range or incompatible (e.g., count outside 1—50, or a seed that is not an integer).
{ "type": "https://api.studyplug.org/errors/validation", "title": "Invalid Parameter", "status": 422, "detail": "'count' must be between 1 and 50", "instance": "/api/v1/generate"}Rate Limited (429)
Section titled “Rate Limited (429)”Returned when the client exceeds the request rate limit. The retryAfter field tells you how long to wait.
{ "type": "https://api.studyplug.org/errors/rate-limited", "title": "Rate Limited", "status": 429, "detail": "Rate limit exceeded. You have exceeded 30 requests per minute.", "instance": "/api/v1/generate", "retryAfter": 23}Internal Error (500)
Section titled “Internal Error (500)”Returned for unexpected server-side failures. These are logged and investigated by the StudyPlug team.
{ "type": "https://api.studyplug.org/errors/internal", "title": "Generation Failed", "status": 500, "detail": "Content generation failed. Please try again.", "instance": "/api/v1/generate"}Handling Errors in Code
Section titled “Handling Errors in Code”TypeScript SDK
Section titled “TypeScript SDK”The SDK throws typed errors that you can catch and inspect:
import { StudyPlug, isStudyPlugError, isRateLimitError, isValidationError } from "studyplug";
const client = new StudyPlug();
try { const result = await client.generate({ skill: "add-within-10", grade: "kindergarten", count: 5, }); console.log(result.data.items);} catch (error) { if (isRateLimitError(error)) { const retryAfter = error.retryAfter ?? 60; console.log(`Rate limited. Retrying in ${retryAfter} seconds...`); } else if (isValidationError(error)) { console.error(`Validation failed: ${error.raw.detail}`); for (const fieldError of error.raw.errors ?? []) { console.error(` Field '${fieldError.field}': ${fieldError.message}`); } } else if (isStudyPlugError(error)) { console.error(`[${error.status}] ${error.raw.title}: ${error.raw.detail}`); }}Raw HTTP
Section titled “Raw HTTP”Check the status code first, then parse the JSON body:
const response = await fetch("https://api.studyplug.org/api/v1/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ skill: "add-within-10", grade: "kindergarten", count: 5 }),});
if (!response.ok) { const error = await response.json(); // error.type, error.title, error.status, error.detail are always present throw new Error(`API error: ${error.detail}`);}
const result = await response.json();Retry Strategy
Section titled “Retry Strategy”A recommended retry pattern for production applications:
import { isRateLimitError, isStudyPlugError } from "studyplug";
async function generateWithRetry(params: Parameters<typeof client.generate>[0], maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await client.generate(params); } catch (error) { if (isRateLimitError(error)) { const wait = (error.retryAfter ?? 60) * 1000; await new Promise((r) => setTimeout(r, wait)); continue; } if (isStudyPlugError(error) && error.status >= 500 && attempt < maxRetries) { await new Promise((r) => setTimeout(r, 1000 * attempt)); continue; } throw error; // 4xx errors (except 429) are not retryable } }}Content-Type Header
Section titled “Content-Type Header”All error responses are returned with Content-Type: application/problem+json as specified by RFC 7807. Most JSON parsers handle this transparently, but if you perform strict content-type checking, allow both application/json and application/problem+json.
Next Steps
Section titled “Next Steps”- Rate Limits — Understand the rate limiting tiers and quotas.
- Content Items — The structure of successful responses.
- SDK Error Handling — Detailed SDK-specific error handling patterns.