TypeScript SDK Reference
Complete reference for every class, method, type, and configuration option in the @puzzle-section/sdk package.
Installation
bash
npm install @puzzle-section/sdkbash
yarn add @puzzle-section/sdkbash
pnpm add @puzzle-section/sdkRequirements
- Node.js 18+ (server-side)
- TypeScript 5.0+ (recommended)
- Modern browsers with ES2020+ support
Configuration
TypeScript
import { PuzzleSectionClient } from '@puzzle-section/sdk';
const client = new PuzzleSectionClient({ apiKey: 'ps_live_xxxxxxxxxxxx', // Required — tenant API key baseUrl: 'https://api.puzzlesection.app', // Optional — default shown timeout: 30000, // Optional — request timeout in ms retryCount: 3, // Optional — max retries on 5xx / 429 fetch: customFetch, // Optional — custom fetch implementation});ClientConfig
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Tenant API key (required). Sent as X-API-Key. |
baseUrl | string | https://api.puzzlesection.app | API base URL. All requests go to {baseUrl}/api/v1{path}. |
timeout | number | 30000 | Request timeout in milliseconds. |
retryCount | number | 3 | Max retries for 5xx and 429 responses. |
fetch | typeof fetch | globalThis.fetch | Custom fetch for environments without native fetch. |
Response Envelope
Every SDK method returns Promise<ResponseWithRateLimit<T>>:
TypeScript
1interface ResponseWithRateLimit<T> {2 data: T;3 rateLimit: RateLimitInfo;4}5
6interface RateLimitInfo {7 limit: number; // Max requests per window8 remaining: number; // Requests remaining9 reset: number; // Unix timestamp when window resets10}11
12// Usage13const { data, rateLimit } = await client.puzzles.getDaily();14console.log(data); // Puzzle[]15console.log(rateLimit.remaining); // 999client.puzzles
getDaily(params?)
Fetch today's daily puzzles with optional filters.
TypeScript
1// All daily puzzles2const { data } = await client.puzzles.getDaily();3// data: Puzzle[]4
5// Filtered by type, difficulty, and date6const { data } = await client.puzzles.getDaily({7 date: '2026-03-05', // YYYY-MM-DD (default: today)8 types: ['sudoku', 'kakuro'], // PuzzleType[]9 difficulties: ['medium', 'hard'], // PuzzleDifficulty[]10});getById(id)
Fetch a single puzzle by its UUID.
TypeScript
1const { data: puzzle } = await client.puzzles.getById('550e8400-e29b-...');2
3// Example response:4// {5// id: '550e8400-e29b-41d4-a716-446655440000',6// type: 'sudoku',7// difficulty: 'medium',8// date: '2026-03-05',9// estimatedTime: 480,10// isPremium: false,11// data: {12// grid: [[0, 0, 3, 0, 2, 0, 6, 0, 0], ...],13// given: [[false, false, true, ...], ...],14// difficulty: 'medium'15// },16// createdAt: '2026-03-04T00:00:00Z',17// updatedAt: '2026-03-04T00:00:00Z'18// }getByType(type, params?)
Fetch a paginated list of puzzles by type.
TypeScript
const { data } = await client.puzzles.getByType('sudoku', { difficulty: 'hard', limit: 10, page: 1,});// data: PaginatedResponse<Puzzle>// data.data: Puzzle[]// data.pagination: { page, limit, total, totalPages }getByDate(date, type, difficulty?)
Fetch a specific puzzle for a date and type.
TypeScript
const { data } = await client.puzzles.getByDate( '2026-03-05', 'sudoku', 'medium');// data: PuzzlegetTypes()
List all available puzzle types.
TypeScript
const { data: types } = await client.puzzles.getTypes();// types: PuzzleType[]// ['sudoku', 'wordsearch', 'crossword', 'nonogram', ...]validateSolution(id, solution)
Validate a user's solution against the correct answer.
TypeScript
1const { data } = await client.puzzles.validateSolution(2 '550e8400-...',3 { grid: [[5, 3, 4, 6, 7, 8, 9, 1, 2], ...] }4);5// data: { valid: boolean, errors?: string[] }6if (data.valid) {7 console.log('Correct!');8} else {9 console.log('Errors:', data.errors);10}client.health
check()
TypeScript
const { data: status } = await client.health.check();// status: HealthStatus// {// status: 'healthy' | 'degraded' | 'unhealthy',// version: '1.0.0',// timestamp: '2026-03-05T12:00:00Z',// checks: { database: 'up' | 'down', redis: 'up' | 'down' }// }ping()
TypeScript
const { data } = await client.health.ping();// { pong: true, timestamp: '2026-03-05T12:00:00Z' }client.admin
Admin methods are scoped to your tenant and are used for puzzle content management. They are typically used by internal tools and the dashboard, not end-user applications.
Puzzle Management
TypeScript
1// List puzzles with filters2const { data } = await client.admin.listPuzzles({3 type: 'nonogram',4 status: 'draft',5 search: 'cat',6 page: 1,7 pageSize: 20,8});9// data: PaginatedResponse<AdminPuzzle>10
11// Create a new puzzle12const { data: puzzle } = await client.admin.createPuzzle({13 title: 'Cat Nonogram',14 type: 'nonogram',15});16
17// Update puzzle metadata18const { data: updated } = await client.admin.updatePuzzle('puzzle-id', {19 title: 'Updated Title',20 status: 'published',21 event_tags: ['spring-2026'],22});23
24// Get a single puzzle25const { data: puzzle } = await client.admin.getPuzzle('puzzle-id');26
27// Lifecycle28await client.admin.publishPuzzle('puzzle-id');29await client.admin.unpublishPuzzle('puzzle-id');30await client.admin.deletePuzzle('puzzle-id'); // soft delete31await client.admin.restorePuzzle('puzzle-id'); // restore from bin32await client.admin.permanentlyDeletePuzzle('puzzle-id');33await client.admin.removeFromLibrary('puzzle-id'); // deactivate all variants34
35// List deleted puzzles36const { data } = await client.admin.listDeletedPuzzles();Variant Management
TypeScript
1// Create or update a puzzle variant2const { data: variant } = await client.admin.upsertVariant('puzzle-id', {3 difficulty: 'medium',4 enabled: true,5 width: 15,6 height: 15,7 grid_data: [[0, 1, 1, ...], ...],8 color_grid: [[0, 1, 2, ...], ...], // for color nonograms9 palette: ['#000', '#ff0000', '#0000ff'],10});11
12// Delete a variant13await client.admin.deleteVariant('puzzle-id', 'variant-id');Evaluation & Filtering
TypeScript
1// Evaluate nonogram fun factor2const { data: result } = await client.admin.evaluateFunFactor({3 grid_data: [[0, 1, 1, ...], ...],4 difficulty: 'medium',5 width: 15,6 height: 15,7});8// result: FunFactorResult9// {10// funScore: 7.8,11// meetsThreshold: true,12// recommendation: 'accept' | 'regenerate' | 'adjust_difficulty',13// estimatedDifficulty: 'medium',14// components: { techniqueDiversity, logicalFlow, ... },15// notes: [...],16// solveStats: { solved, steps, techniquesUsed, ... },17// report: '...'18// }19
20// Filter a word from a wordsearch puzzle21const { data } = await client.admin.filterWordFromPuzzle('puzzle-id', {22 word: 'offensive',23 reason: 'inappropriate',24 addedByName: 'Content Moderator',25 addedByEmail: 'mod@example.com',26});Error Handling
The SDK throws typed error classes that extend ApiError. Use instanceof to branch on error type:
TypeScript
1import {2 PuzzleSectionClient,3 ApiError,4 AuthenticationError,5 RateLimitError,6 NotFoundError,7 ValidationError,8 ServerError,9} from '@puzzle-section/sdk';10
11try {12 const { data } = await client.puzzles.getById('nonexistent');13} catch (error) {14 if (error instanceof RateLimitError) {15 // 429 — retry after the specified delay16 console.log(`Retry after ${error.retryAfter}s`);17 console.log(`Limit: ${error.limit}, Remaining: ${error.remaining}`);18
19 } else if (error instanceof AuthenticationError) {20 // 401 — invalid or missing API key21 console.error('Check your API key configuration');22
23 } else if (error instanceof NotFoundError) {24 // 404 — resource does not exist25 console.log(error.message);26 // "Puzzle with ID 'nonexistent' not found"27
28 } else if (error instanceof ValidationError) {29 // 400 — request validation failed30 console.log(error.details);31 // { field: ['must be a valid UUID'] }32
33 } else if (error instanceof ServerError) {34 // 500/502/503/504 — server error (retried automatically)35 console.error('Server error after retries exhausted');36
37 } else if (error instanceof ApiError) {38 // Catch-all for any other API error39 console.error(`[${error.code}] ${error.message}`);40 console.log('Status:', error.statusCode);41 }42}Error Class Hierarchy
| Class | HTTP Status | Properties |
|---|---|---|
ApiError | any | message, code, statusCode, details? |
AuthenticationError | 401 | message (default: "Invalid or missing API key") |
ValidationError | 400 | message, details: Record<string, string[]> |
NotFoundError | 404 | message (includes resource type and ID) |
RateLimitError | 429 | retryAfter, limit, remaining, reset |
ServerError | 500–504 | message (thrown after retries exhausted) |
Retry Behaviour
- 5xx errors are retried up to
retryCounttimes with exponential backoff (2attempt seconds). - 429 errors are retried using the
Retry-Afterheader value when present. - 4xx errors (except 429) are not retried.
- Timeouts throw an
ApiErrorwith codeTIMEOUT(status 408). - Network failures throw an
ApiErrorwith codeNETWORK_ERROR(status 0).
TypeScript Types
All types are exported from the package:
TypeScript
1import type {2 // Puzzle types3 Puzzle,4 PuzzleType, // 'sudoku' | 'wordsearch' | 'crossword' | ... (14 types)5 PuzzleDifficulty, // 'easy' | 'medium' | 'hard' | 'expert'6 PuzzleData, // SudokuData | WordsearchData | CrosswordData | NonogramData | Record<string, unknown>7 SudokuData, // { grid: number[][], given: boolean[][], difficulty }8 WordsearchData, // { grid: string[][], words: string[], theme?, difficulty }9 CrosswordData, // { grid, clues: { across, down }, numbering, difficulty }10 NonogramData, // { rows: number[][], cols: number[][], size: { width, height }, difficulty }11
12 // Response types13 RateLimitInfo,14 ResponseWithRateLimit,15 ApiResponse,16 PaginatedResponse, // { data: T[], pagination: { page, limit, total, totalPages } }17
18 // Health types19 HealthStatus,20
21 // Admin types22 AdminPuzzle,23 AdminPuzzleVariant,24 CreateAdminPuzzleRequest,25 UpdateAdminPuzzleRequest,26 UpsertPuzzleVariantRequest,27 FunFactorResult,28 FilterWordRequest,29 FilterWordResult,30
31 // Config32 ClientConfig,33} from '@puzzle-section/sdk';