Skip to main content

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/sdk
bash
yarn add @puzzle-section/sdk
bash
pnpm add @puzzle-section/sdk

Requirements

  • 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

OptionTypeDefaultDescription
apiKeystringTenant API key (required). Sent as X-API-Key.
baseUrlstringhttps://api.puzzlesection.appAPI base URL. All requests go to {baseUrl}/api/v1{path}.
timeoutnumber30000Request timeout in milliseconds.
retryCountnumber3Max retries for 5xx and 429 responses.
fetchtypeof fetchglobalThis.fetchCustom 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 window
8 remaining: number; // Requests remaining
9 reset: number; // Unix timestamp when window resets
10}
11
12// Usage
13const { data, rateLimit } = await client.puzzles.getDaily();
14console.log(data); // Puzzle[]
15console.log(rateLimit.remaining); // 999

client.puzzles

getDaily(params?)

Fetch today's daily puzzles with optional filters.

TypeScript
1// All daily puzzles
2const { data } = await client.puzzles.getDaily();
3// data: Puzzle[]
4
5// Filtered by type, difficulty, and date
6const { 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: Puzzle

getTypes()

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 filters
2const { 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 puzzle
12const { data: puzzle } = await client.admin.createPuzzle({
13 title: 'Cat Nonogram',
14 type: 'nonogram',
15});
16
17// Update puzzle metadata
18const { 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 puzzle
25const { data: puzzle } = await client.admin.getPuzzle('puzzle-id');
26
27// Lifecycle
28await client.admin.publishPuzzle('puzzle-id');
29await client.admin.unpublishPuzzle('puzzle-id');
30await client.admin.deletePuzzle('puzzle-id'); // soft delete
31await client.admin.restorePuzzle('puzzle-id'); // restore from bin
32await client.admin.permanentlyDeletePuzzle('puzzle-id');
33await client.admin.removeFromLibrary('puzzle-id'); // deactivate all variants
34
35// List deleted puzzles
36const { data } = await client.admin.listDeletedPuzzles();

Variant Management

TypeScript
1// Create or update a puzzle variant
2const { 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 nonograms
9 palette: ['#000', '#ff0000', '#0000ff'],
10});
11
12// Delete a variant
13await client.admin.deleteVariant('puzzle-id', 'variant-id');

Evaluation & Filtering

TypeScript
1// Evaluate nonogram fun factor
2const { data: result } = await client.admin.evaluateFunFactor({
3 grid_data: [[0, 1, 1, ...], ...],
4 difficulty: 'medium',
5 width: 15,
6 height: 15,
7});
8// result: FunFactorResult
9// {
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 puzzle
21const { 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 delay
16 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 key
21 console.error('Check your API key configuration');
22
23 } else if (error instanceof NotFoundError) {
24 // 404 — resource does not exist
25 console.log(error.message);
26 // "Puzzle with ID 'nonexistent' not found"
27
28 } else if (error instanceof ValidationError) {
29 // 400 — request validation failed
30 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 error
39 console.error(`[${error.code}] ${error.message}`);
40 console.log('Status:', error.statusCode);
41 }
42}

Error Class Hierarchy

ClassHTTP StatusProperties
ApiErroranymessage, code, statusCode, details?
AuthenticationError401message (default: "Invalid or missing API key")
ValidationError400message, details: Record<string, string[]>
NotFoundError404message (includes resource type and ID)
RateLimitError429retryAfter, limit, remaining, reset
ServerError500–504message (thrown after retries exhausted)

Retry Behaviour

  • 5xx errors are retried up to retryCount times with exponential backoff (2attempt seconds).
  • 429 errors are retried using the Retry-After header value when present.
  • 4xx errors (except 429) are not retried.
  • Timeouts throw an ApiError with code TIMEOUT (status 408).
  • Network failures throw an ApiError with code NETWORK_ERROR (status 0).

TypeScript Types

All types are exported from the package:

TypeScript
1import type {
2 // Puzzle types
3 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 types
13 RateLimitInfo,
14 ResponseWithRateLimit,
15 ApiResponse,
16 PaginatedResponse, // { data: T[], pagination: { page, limit, total, totalPages } }
17
18 // Health types
19 HealthStatus,
20
21 // Admin types
22 AdminPuzzle,
23 AdminPuzzleVariant,
24 CreateAdminPuzzleRequest,
25 UpdateAdminPuzzleRequest,
26 UpsertPuzzleVariantRequest,
27 FunFactorResult,
28 FilterWordRequest,
29 FilterWordResult,
30
31 // Config
32 ClientConfig,
33} from '@puzzle-section/sdk';