Rate Limits
Every request to the StudyPlug API includes rate limit information in the response headers. When you exceed the limit, the API returns a 429 Too Many Requests response with details on when to retry.
Limits by Tier
Section titled “Limits by Tier”| Tier | Per Minute | Per Day | Keyed By |
|---|---|---|---|
| Anonymous (no API key) | 30 | 200 | IP address |
| Free (API key) | 30 | 500 | API key |
| Pro (API key) | 120 | 10,000 | API key |
To get an API key, submit a request. See Authentication for details.
Response Headers
Section titled “Response Headers”Every successful response includes these headers:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window | 30 |
X-RateLimit-Remaining | Requests remaining in the current window | 14 |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets | 1708700000 |
When rate-limited, the response also includes:
| Header | Description | Example |
|---|---|---|
Retry-After | Seconds to wait before retrying | 8 |
429 Response Body
Section titled “429 Response Body”Rate-limited responses use the RFC 7807 Problem Details format:
{ "type": "https://api.studyplug.org/errors/rate-limited", "title": "Rate Limit Exceeded", "status": 429, "detail": "You have exceeded 30 requests per minute. Please wait before retrying.", "instance": "/api/v1/generate", "retryAfter": 8}The retryAfter field in the body matches the Retry-After header value and indicates the number of seconds to wait.
SDK Auto-Retry
Section titled “SDK Auto-Retry”The TypeScript SDK automatically retries on 429 responses with exponential backoff. It reads the Retry-After header (or the retryAfter body field) to determine how long to wait.
import { StudyPlug, isRateLimitError } from "studyplug";
const sp = new StudyPlug({ maxRetries: 2, // default: 2 retries on 429});
try { const { data } = await sp.generate({ skill: "add-within-10", count: 5 });} catch (err) { if (isRateLimitError(err)) { // All retries exhausted -- err.retryAfter has the wait time in seconds console.log(`Rate limited. Retry after ${err.retryAfter}s`); }}Set maxRetries: 0 to disable automatic retries and handle 429 responses yourself.
Best Practices
Section titled “Best Practices”Use seeds to leverage caching. Seeded requests with the same parameters return cached results instantly and are much faster than fresh generation. If you need the same content more than once, always pass a seed.
# This response is cached server-side after the first callcurl -X POST https://api.studyplug.org/api/v1/generate \ -H "Content-Type: application/json" \ -d '{"skill": "add-within-10", "count": 10, "seed": 42}'Cache responses on your side. Store generated content in your own database or cache layer. Content items are static data — they do not change once generated with a given seed.
Batch instead of looping. Use the count parameter (up to 50) to get multiple items in a single request rather than making one request per item.
// Good: one request for 20 itemsconst { data } = await sp.generate({ skill: "add-within-10", count: 20 });
// Avoid: 20 separate requestsfor (let i = 0; i < 20; i++) { await sp.generate.single({ skill: "add-within-10" }); // wastes rate limit}Monitor the headers. Check X-RateLimit-Remaining in your responses to proactively slow down before hitting the limit, rather than relying on retries after a 429.