Complete reference for all API endpoints in the Pokedex Field Log Generator.
All API endpoints are relative to the application root:
http://localhost:3000/api
All endpoints return JSON responses with appropriate HTTP status codes.
Success Response:
{
"success": true,
"data": { ... }
}Error Response:
{
"success": false,
"error": "Error message description"
}Manage background processing jobs for batch generation.
Create a new processing job.
POST /api/jobsRequest Body:
{
"mode": "FULL" | "SUMMARY_ONLY" | "AUDIO_ONLY",
"generationId": number,
"region": "Kanto" | "Johto" | ...,
"voice": "Kore" | "Zephyr" | "Charon" | "Puck" | "Fenrir",
"pokemonIds": number[]
}Response:
{
"success": true,
"data": {
"id": "uuid-string"
}
}Retrieve current status and progress of a job.
GET /api/jobs/{id}Response:
{
"success": true,
"data": {
"id": "uuid-string",
"status": "running" | "queued" | "paused" | "completed" | "failed" | "canceled",
"stage": "summary" | "audio",
"mode": "FULL",
"generationId": 1,
"region": "Kanto",
"voice": "Kore",
"total": 151,
"current": 45,
"message": "Processing Pokemon 45 of 151",
"cooldownUntil": "2025-01-15T12:05:00.000Z" | null,
"error": null | "Error message",
"retryCount": 0,
"pokemonIds": [1, 2, 3, ...],
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:05:00.000Z"
}
}Pause a running job. Also emits a paused SSE event for instant client notification.
POST /api/jobs/{id}/pauseResponse:
{
"success": true,
"data": { "paused": true }
}Resume a paused job. Also emits a resumed SSE event for instant client notification.
POST /api/jobs/{id}/resumeResponse:
{
"success": true,
"data": { "resumed": true }
}Cancel a job. Also emits a canceled SSE event for instant client notification.
POST /api/jobs/{id}/cancelResponse:
{
"success": true,
"data": { "canceled": true }
}Open a Server-Sent Events stream for real-time job progress updates. The server sends the current job state immediately on connect, then pushes events as they occur. The stream closes automatically on terminal events (completed, failed, canceled).
GET /api/jobs/{id}/streamHeaders:
Content-Type: text/event-stream
Cache-Control: no-cache, no-transform
Connection: keep-alive
Event Format:
Each event is a JSON object sent as an SSE data field:
data: {"type":"progress","jobId":"...","status":"running","stage":"summary","current":3,"total":10,"message":"Generating summary for #4...","cooldownUntil":null}
data: {"type":"completed","jobId":"...","generationId":1,"pokemonIds":[1,2,3],"mode":"FULL"}
data: {"type":"failed","jobId":"...","error":"Daily API quota exceeded...","generationId":1,"pokemonIds":[1,2,3],"mode":"FULL"}
data: {"type":"canceled","jobId":"..."}
data: {"type":"paused","jobId":"..."}
data: {"type":"resumed","jobId":"..."}
A keepalive comment (: keepalive) is sent every 30 seconds to prevent proxy/browser timeouts.
Client Usage:
const es = new EventSource(`/api/jobs/${jobId}/stream`);
es.onmessage = (event) => {
const data = JSON.parse(event.data);
// Handle data.type: 'progress' | 'completed' | 'failed' | 'canceled' | 'paused' | 'resumed'
};Pause all currently running jobs.
POST /api/jobs/maintenance/pause-allResponse:
{
"success": true,
"data": { "pausedCount": 3 }
}Cancel all currently running jobs.
POST /api/jobs/maintenance/cancel-allResponse:
{
"success": true,
"data": { "canceledCount": 3 }
}Recover jobs stuck in running state beyond the stalled threshold (5 minutes).
POST /api/jobs/maintenance/recoverResponse:
{
"success": true,
"data": { "recoveredCount": 1 }
}Fetch and cache Pokemon data from PokeAPI.
Retrieve cached Pokemon data or fetch from PokeAPI.
GET /api/pokemon/{id}Response:
{
"success": true,
"data": {
"id": 1,
"name": "bulbasaur",
"displayName": "Bulbasaur",
"height": 7,
"weight": 69,
"types": ["grass", "poison"],
"habitat": "grassland",
"flavorTexts": [
"A strange seed was planted on its back at birth...",
"It can go for days without eating a single morsel..."
],
"moveNames": ["tackle", "vine-whip", "razor-leaf", ...],
"imagePngPath": "/pokemon/1.png",
"imageSvgPath": "/pokemon/1.svg",
"generationId": 1,
"region": "Kanto",
"speciesId": 1,
"isDefault": true,
"formName": null,
"variantCategory": "default",
"regionName": null,
"cachedAt": "2025-01-15T12:00:00.000Z"
}
}Returns { "success": true, "data": null } if not cached.
Cache Pokemon data and download sprites.
POST /api/pokemon/{id}Request Body:
{
"id": 1,
"name": "bulbasaur",
"displayName": "Bulbasaur",
"height": 7,
"weight": 69,
"types": ["grass", "poison"],
"habitat": "grassland",
"flavorTexts": ["..."],
"moveNames": ["tackle", "vine-whip"],
"imagePngUrl": "https://...",
"imageSvgUrl": "https://...",
"generationId": 1,
"region": "Kanto",
"speciesId": 1,
"isDefault": true,
"formName": null,
"variantCategory": "default",
"regionName": null
}Response:
{
"success": true,
"data": { "cached": true }
}Manage generated field log summaries.
Retrieve all summaries, optionally filtered by generation.
GET /api/summaries?generationId={number}Query Parameters:
generationId(optional) - Filter by generation (1-9)
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "Bulbasaur",
"summary": "Pokemon trainer log 1. Emerald blades of tall grass...",
"region": "Kanto",
"generationId": 1,
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
},
...
]
}Retrieve a specific summary by Pokemon ID.
GET /api/summaries/{id}Response:
{
"success": true,
"data": {
"id": 1,
"name": "Bulbasaur",
"summary": "Pokemon trainer log 1. Emerald blades of tall grass...",
"region": "Kanto",
"generationId": 1,
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
}Save a new summary or update an existing one.
POST /api/summariesRequest Body:
{
"id": 1,
"name": "Bulbasaur",
"summary": "Pokemon trainer log 1. Emerald blades of tall grass...",
"region": "Kanto",
"generationId": 1
}Response:
{
"success": true,
"data": { "saved": true }
}Delete a summary by Pokemon ID.
DELETE /api/summaries/{id}Response:
{
"success": true,
"data": { "deleted": true }
}Delete multiple summaries by IDs.
DELETE /api/summariesRequest Body:
{
"ids": [1, 2, 3]
}Response:
{
"success": true,
"data": { "deleted": 3 }
}Manage generated audio narrations.
Retrieve metadata for all audio logs, optionally filtered by generation. The audioBase64 field is excluded from list responses to prevent large payloads.
GET /api/audio?generationId={number}Query Parameters:
generationId(optional) - Filter by generation (1-9)
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "Bulbasaur",
"region": "Kanto",
"generationId": 1,
"voice": "Kore",
"audioFormat": "mp3",
"bitrate": 128,
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
},
...
]
}Retrieve a specific audio log by Pokemon ID (includes full audio data).
GET /api/audio/{id}Response:
{
"success": true,
"data": {
"id": 1,
"name": "Bulbasaur",
"region": "Kanto",
"generationId": 1,
"voice": "Kore",
"audioBase64": "base64-encoded-mp3-data...",
"audioFormat": "mp3",
"bitrate": 128,
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
}Save a new audio log or update an existing one.
POST /api/audioRequest Body:
{
"id": 1,
"name": "Bulbasaur",
"region": "Kanto",
"generationId": 1,
"voice": "Kore",
"audioBase64": "base64-encoded-mp3-data...",
"audioFormat": "mp3",
"bitrate": 128
}Response:
{
"success": true,
"data": { "saved": true }
}Delete an audio log by Pokemon ID.
DELETE /api/audio/{id}Response:
{
"success": true,
"data": { "deleted": true }
}Delete multiple audio logs by IDs.
DELETE /api/audioRequest Body:
{
"ids": [1, 2, 3]
}Response:
{
"success": true,
"data": { "deleted": 3 }
}Manage custom prompt overrides for AI generation.
Retrieve all stored prompts.
GET /api/promptsResponse:
{
"success": true,
"data": [
{
"type": "summary",
"content": "Custom summary prompt...",
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
},
{
"type": "tts",
"content": "Custom TTS prompt...",
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
]
}Retrieve a specific prompt by type.
GET /api/prompts/{type}Parameters:
type- "summary" or "tts"
Response:
{
"success": true,
"data": {
"type": "summary",
"content": "Custom summary prompt...",
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
}Returns { "success": true, "data": null } if no override exists.
Save a new prompt or update an existing one.
POST /api/promptsRequest Body:
{
"type": "summary" | "tts",
"content": "Custom prompt content..."
}Response:
{
"success": true,
"data": { "saved": true }
}Delete a prompt by type (reverts to default).
DELETE /api/prompts?type={type}Query Parameters:
type- "summary" or "tts"
Response:
{
"success": true,
"data": { "deleted": true }
}type JobStatus =
| "queued" // Waiting to be processed
| "running" // Currently processing
| "paused" // Paused by user
| "completed" // Successfully completed
| "failed" // Failed with error
| "canceled"; // Canceled by usertype JobStage =
| "summary" // Generating text summaries
| "audio"; // Generating audio narrationtype JobMode =
| "FULL" // Generate summaries and audio
| "SUMMARY_ONLY" // Generate summaries only
| "AUDIO_ONLY"; // Generate audio from existing summariestype VoiceProfile =
| "Kore" // Sophisticated female voice
| "Zephyr" // Professional female voice
| "Charon" // Resonant male voice
| "Puck" // Youthful male voice
| "Fenrir"; // Rugged male voiceconst GENERATION_REGIONS: Record<number, string> = {
1: "Kanto",
2: "Johto",
3: "Hoenn",
4: "Sinnoh",
5: "Unova",
6: "Kalos",
7: "Alola",
8: "Galar",
9: "Paldea"
};The application enforces server-side rate limits to comply with Gemini API restrictions:
- Summary Generation: 15-second cooldown between each Pokemon (with ±20% jitter)
- TTS Generation: 15-second cooldown between each Pokemon (with ±20% jitter), one TTS call per Pokemon
- Concurrency: Up to 3 concurrent summary jobs, 1 concurrent audio job
Cooldown information is included in job status responses via the cooldownUntil field.
| Status Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid parameters |
| 404 | Not Found - Resource does not exist |
| 500 | Internal Server Error - Server-side error |
Currently, the application does not require authentication. All endpoints are accessible without credentials. The Gemini API key is stored server-side and never exposed to clients.
The API is designed for same-origin requests only. Cross-origin requests are not supported in the default configuration.
The API does not currently use versioning. Breaking changes will be documented in release notes.