PuzzleSection.Client
1.0.1
dotnet add package PuzzleSection.Client --version 1.0.1
NuGet\Install-Package PuzzleSection.Client -Version 1.0.1
<PackageReference Include="PuzzleSection.Client" Version="1.0.1" />
<PackageVersion Include="PuzzleSection.Client" Version="1.0.1" />
<PackageReference Include="PuzzleSection.Client" />
paket add PuzzleSection.Client --version 1.0.1
#r "nuget: PuzzleSection.Client, 1.0.1"
#:package PuzzleSection.Client@1.0.1
#addin nuget:?package=PuzzleSection.Client&version=1.0.1
#tool nuget:?package=PuzzleSection.Client&version=1.0.1
PuzzleSection.Client
Official C# SDK for the Puzzle Section Platform API.
The SDK provides typed access to:
- Puzzle content — daily puzzles, by type, by ID, solution validation
- Admin tools — puzzle and variant management, content moderation
- Health monitoring — API status and connectivity checks
User management and progress tracking are tenant responsibilities. Implement your own backend for user-specific features.
Installation
dotnet add package PuzzleSection.Client
Requirements: .NET 8.0+
Quick Start
using PuzzleSection.Client;
using var client = new PuzzleSectionClient("ps_live_xxxxxxxxxxxx");
var { Data: puzzles } = await client.Puzzles.GetDailyAsync();
Configuration
using var client = new PuzzleSectionClient(new ClientOptions
{
ApiKey = "ps_live_xxxxxxxxxxxx", // Required — tenant API key
BaseUrl = "https://api.puzzlesection.app", // Optional — default shown
Timeout = TimeSpan.FromSeconds(30), // Optional — request timeout
RetryCount = 3, // Optional — max retries on 5xx / 429
HttpClient = existingHttpClient, // Optional — custom HttpClient
});
| 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 |
TimeSpan |
30s | Request timeout. Ignored when a custom HttpClient is supplied. |
RetryCount |
int |
3 |
Max retries for 5xx and 429 responses. |
HttpClient |
HttpClient? |
null |
Custom HttpClient. When provided, the SDK does not own or dispose it. |
PuzzleSectionClientimplementsIDisposable. Useusingor dispose it explicitly. When a customHttpClientis supplied it is not disposed.
Response Envelope
Every SDK method returns Task<ResponseWithRateLimit<T>>:
var response = await client.Puzzles.GetDailyAsync();
Puzzle[] puzzles = response.Data; // the response data
int remaining = response.RateLimit.Remaining; // requests left in the window
long resetAt = response.RateLimit.Reset; // Unix timestamp when it resets
API Reference
client.Puzzles
// All daily puzzles
var { Data: puzzles } = await client.Puzzles.GetDailyAsync();
// Filtered by type, difficulty, and date
var { Data: filtered } = await client.Puzzles.GetDailyAsync(
date: "2026-03-05",
types: [PuzzleType.Sudoku, PuzzleType.Kakuro],
difficulties: [PuzzleDifficulty.Medium, PuzzleDifficulty.Hard]);
// Single puzzle by UUID
var { Data: puzzle } = await client.Puzzles.GetByIdAsync("550e8400-e29b-...");
// Paginated list by type
var { Data: page } = await client.Puzzles.GetByTypeAsync(
PuzzleType.Sudoku, difficulty: PuzzleDifficulty.Hard, page: 1, limit: 10);
// page.Data: IReadOnlyList<Puzzle>
// page.Pagination: { Page, Limit, Total, TotalPages }
// Specific puzzle for a date and type
var { Data: sudoku } = await client.Puzzles.GetByDateAsync("2026-03-05", PuzzleType.Sudoku);
// All available types
var { Data: types } = await client.Puzzles.GetTypesAsync();
// Validate a solution
var { Data: result } = await client.Puzzles.ValidateSolutionAsync(
"550e8400-...", new { grid = solutionGrid });
if (result.Valid)
Console.WriteLine("Correct!");
else
Console.WriteLine(string.Join(", ", result.Errors ?? []));
client.Health
// Full health check
var { Data: status } = await client.Health.CheckAsync();
Console.WriteLine(status.Status); // "healthy" | "degraded" | "unhealthy"
Console.WriteLine(status.Checks?.Database); // "up" | "down"
// Simple connectivity ping
var { Data: ping } = await client.Health.PingAsync();
Console.WriteLine(ping.Pong); // true
client.Admin
Admin methods are tenant-scoped and intended for puzzle content management — typically used by internal tools and the dashboard, not end-user applications.
// List puzzles
var { Data: page } = await client.Admin.ListPuzzlesAsync(
type: AdminPuzzleType.Nonogram, status: AdminPuzzleStatus.Draft, page: 1, pageSize: 20);
// Create a puzzle
var { Data: puzzle } = await client.Admin.CreatePuzzleAsync(new CreateAdminPuzzleRequest
{
Title = "Cat Nonogram",
Type = AdminPuzzleType.Nonogram,
});
// Update puzzle metadata
var { Data: updated } = await client.Admin.UpdatePuzzleAsync("puzzle-id", new UpdateAdminPuzzleRequest
{
Title = "Updated Title",
Status = AdminPuzzleStatus.Published,
EventTags = ["spring-2026"],
});
// Get a single puzzle
var { Data: puzzle } = await client.Admin.GetPuzzleAsync("puzzle-id");
// Lifecycle
await client.Admin.PublishPuzzleAsync("puzzle-id");
await client.Admin.UnpublishPuzzleAsync("puzzle-id");
await client.Admin.DeletePuzzleAsync("puzzle-id"); // soft delete
await client.Admin.RestorePuzzleAsync("puzzle-id"); // restore from bin
await client.Admin.PermanentlyDeletePuzzleAsync("puzzle-id"); // irreversible
await client.Admin.RemoveFromLibraryAsync("puzzle-id"); // deactivate all variants
// List deleted puzzles
var { Data: bin } = await client.Admin.ListDeletedPuzzlesAsync();
// Create or update a variant
var { Data: variant } = await client.Admin.UpsertVariantAsync("puzzle-id", new UpsertPuzzleVariantRequest
{
Difficulty = PuzzleDifficulty.Medium,
Enabled = true,
Width = 15,
Height = 15,
GridData = gridData,
});
// Delete a variant
await client.Admin.DeleteVariantAsync("puzzle-id", "variant-id");
// Evaluate fun factor
var { Data: eval } = await client.Admin.EvaluateFunFactorAsync(new EvaluateFunFactorRequest
{
GridData = gridData,
Difficulty = PuzzleDifficulty.Medium,
Width = 15,
Height = 15,
});
Console.WriteLine($"Score: {eval.FunScore}, Meets threshold: {eval.MeetsThreshold}");
// Filter an offensive word from a wordsearch
await client.Admin.FilterWordFromPuzzleAsync("puzzle-id", new FilterWordRequest
{
Word = "offensive",
Reason = "Inappropriate content",
AddedByName = "Content Moderator",
AddedByEmail = "mod@example.com",
});
Error Handling
The SDK throws typed exceptions that inherit from ApiException. Use catch blocks with is patterns:
using PuzzleSection.Client.Exceptions;
try
{
var { Data: puzzle } = await client.Puzzles.GetByIdAsync("nonexistent");
}
catch (RateLimitException ex)
{
// HTTP 429 — SDK retries automatically; thrown when retries are exhausted
Console.WriteLine($"Rate limited. Retry after {ex.RetryAfter}s");
Console.WriteLine($"Limit: {ex.Limit}, Remaining: {ex.Remaining}");
}
catch (AuthenticationException ex)
{
// HTTP 401 — invalid or missing API key
Console.Error.WriteLine("Check your API key configuration");
}
catch (NotFoundException ex)
{
// HTTP 404 — resource does not exist
Console.WriteLine($"{ex.ResourceType} '{ex.ResourceId}' not found");
}
catch (ValidationException ex)
{
// HTTP 400 — request validation failed
foreach (var (field, errors) in ex.ValidationErrors)
Console.WriteLine($"{field}: {string.Join(", ", errors)}");
}
catch (ServerException ex)
{
// HTTP 500–504 — thrown after retries are exhausted
Console.Error.WriteLine($"Server error: {ex.Message}");
}
catch (ApiException ex)
{
// Catch-all for any other API error
Console.Error.WriteLine($"[{ex.Code}] {ex.Message} (HTTP {ex.StatusCode})");
}
| Exception | HTTP Status | Extra Properties |
|---|---|---|
ApiException |
any | Code, StatusCode |
AuthenticationException |
401 | — |
ValidationException |
400 | ValidationErrors: IReadOnlyDictionary<string, string[]> |
NotFoundException |
404 | ResourceType, ResourceId |
RateLimitException |
429 | RetryAfter, Limit, Remaining, Reset |
ServerException |
500–504 | — |
Retry behaviour:
- 5xx errors are retried up to
RetryCounttimes with exponential backoff (2<sup>attempt</sup> seconds). - 429 errors are retried using the
Retry-Afterresponse header value. - 4xx errors (except 429) are not retried.
- Timeouts throw
ApiExceptionwith codeTIMEOUT(status 408). - Network failures throw
ApiExceptionwith codeNETWORK_ERROR(status 0).
Available Puzzle Types
Use the PuzzleType constants class:
| Constant | Value |
|---|---|
PuzzleType.Sudoku |
sudoku |
PuzzleType.Wordsearch |
wordsearch |
PuzzleType.Crossword |
crossword |
PuzzleType.Nonogram |
nonogram |
PuzzleType.ColorNonogram |
colornonogram |
PuzzleType.PicturePath |
picturepath |
PuzzleType.CrossMath |
crossmath |
PuzzleType.Kakuro |
kakuro |
PuzzleType.Slitherlink |
slitherlink |
PuzzleType.Mosaic |
mosaic |
PuzzleType.WordPath |
wordpath |
PuzzleType.WordLadder |
wordladder |
PuzzleType.Breadcrumb |
breadcrumb |
PuzzleType.Hashi |
hashi |
Difficulty constants are on PuzzleDifficulty: Easy, Medium, Hard, Expert.
Creating a NuGet package
dotnet pack --configuration Release
The .nupkg file is written to ./nupkg/.
License
Apache 2.0 — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- System.Net.Http.Json (>= 8.0.0)
- System.Text.Json (>= 8.0.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.