PuzzleSection.Client 1.0.1

dotnet add package PuzzleSection.Client --version 1.0.1
                    
NuGet\Install-Package PuzzleSection.Client -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PuzzleSection.Client" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PuzzleSection.Client" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="PuzzleSection.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PuzzleSection.Client --version 1.0.1
                    
#r "nuget: PuzzleSection.Client, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PuzzleSection.Client@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PuzzleSection.Client&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=PuzzleSection.Client&version=1.0.1
                    
Install as a Cake Tool

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.

PuzzleSectionClient implements IDisposable. Use using or dispose it explicitly. When a custom HttpClient is 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 RetryCount times with exponential backoff (2<sup>attempt</sup> seconds).
  • 429 errors are retried using the Retry-After response header value.
  • 4xx errors (except 429) are not retried.
  • Timeouts throw ApiException with code TIMEOUT (status 408).
  • Network failures throw ApiException with code NETWORK_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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 114 3/6/2026
1.0.0 109 3/5/2026