Skip to content

ExternalEngine

dblike edited this page Jan 20, 2026 · 1 revision

External Engine API

Register and manage external analysis engines for use on Lichess.

Namespace: LichessSharp.Api.Contracts Access: client.ExternalEngine

Warning: This API is in alpha and subject to change.


Overview

The External Engine API allows you to:

  1. Register custom chess engines to use on Lichess
  2. Provide analysis through a provider service
  3. Request analysis from registered engines

This is useful for:

  • Running powerful engines locally while analyzing on Lichess
  • Providing specialized analysis engines
  • Using engines not available on Lichess servers

Engine Registration

ListAsync

List all external engines registered for the authenticated user.

Required Scope: engine:read

Task<IReadOnlyList<ExternalEngine>> ListAsync(CancellationToken cancellationToken = default)

Example:

using var client = new LichessClient(token);

var engines = await client.ExternalEngine.ListAsync();

Console.WriteLine($"Registered engines ({engines.Count}):\n");
foreach (var engine in engines)
{
    Console.WriteLine($"Name: {engine.Name}");
    Console.WriteLine($"  ID: {engine.Id}");
    Console.WriteLine($"  Max Threads: {engine.MaxThreads}");
    Console.WriteLine($"  Max Hash: {engine.MaxHash} MB");
    Console.WriteLine($"  Variants: {string.Join(", ", engine.Variants)}");
    Console.WriteLine();
}

CreateAsync

Register a new external engine.

Required Scope: engine:write

Task<ExternalEngine> CreateAsync(
    ExternalEngineRegistration registration,
    CancellationToken cancellationToken = default)

Example:

using var client = new LichessClient(token);

var engine = await client.ExternalEngine.CreateAsync(new ExternalEngineRegistration
{
    Name = "My Custom Stockfish",
    MaxThreads = 8,
    MaxHash = 4096,
    ProviderSecret = "your-secret-key-at-least-16-chars",
    Variants = new[] { "standard", "chess960" },
    ProviderData = "my-local-engine-v1"
});

Console.WriteLine($"Engine registered!");
Console.WriteLine($"  ID: {engine.Id}");
Console.WriteLine($"  Client Secret: {engine.ClientSecret}");
// Store the client secret securely - you'll need it for analysis requests

GetAsync

Get details of a specific external engine.

Required Scope: engine:read

Task<ExternalEngine> GetAsync(string engineId, CancellationToken cancellationToken = default)

UpdateAsync

Update an existing external engine.

Required Scope: engine:write

Task<ExternalEngine> UpdateAsync(
    string engineId,
    ExternalEngineRegistration registration,
    CancellationToken cancellationToken = default)

Example:

using var client = new LichessClient(token);

var updated = await client.ExternalEngine.UpdateAsync("engineId", new ExternalEngineRegistration
{
    Name = "My Upgraded Stockfish",
    MaxThreads = 16,
    MaxHash = 8192,
    ProviderSecret = "your-secret-key-at-least-16-chars",
    Variants = new[] { "standard", "chess960", "atomic" }
});

DeleteAsync

Delete an external engine.

Required Scope: engine:write

Task DeleteAsync(string engineId, CancellationToken cancellationToken = default)

Analysis Requests

AnalyseAsync

Request analysis from an external engine. Streams analysis lines as they're computed.

IAsyncEnumerable<EngineAnalysisLine> AnalyseAsync(
    string engineId,
    EngineAnalysisRequest request,
    CancellationToken cancellationToken = default)

Example:

using var client = new LichessClient(token);

var request = new EngineAnalysisRequest
{
    ClientSecret = "your-client-secret",
    Work = new EngineAnalysisWork
    {
        SessionId = "my-session",
        Threads = 4,
        Hash = 1024,
        MultiPv = 3,
        InitialFen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
        Moves = new[] { "e7e5", "g1f3" },
        InfiniteDepth = 30
    }
};

await foreach (var line in client.ExternalEngine.AnalyseAsync("engineId", request))
{
    if (line.Pv != null)
    {
        var evalStr = line.Score?.Centipawns.HasValue == true
            ? $"cp {line.Score.Centipawns}"
            : $"mate {line.Score?.Mate}";

        Console.WriteLine($"depth {line.Depth} {evalStr} pv {line.Pv}");
    }
}

Engine Provider

To provide analysis, your engine service needs to:

  1. Poll for work using AcquireWorkAsync
  2. Submit analysis results using SubmitWorkAsync

AcquireWorkAsync

Wait for an analysis request (long polling).

Task<EngineWork?> AcquireWorkAsync(
    string providerSecret,
    CancellationToken cancellationToken = default)

SubmitWorkAsync

Submit analysis results for acquired work.

Task SubmitWorkAsync(
    string workId,
    IAsyncEnumerable<string> uciLines,
    CancellationToken cancellationToken = default)

Provider Example:

using var client = new LichessClient();

var providerSecret = "your-provider-secret";

// Engine provider loop
while (!cancellationToken.IsCancellationRequested)
{
    // Wait for work (long polling)
    var work = await client.ExternalEngine.AcquireWorkAsync(providerSecret, cancellationToken);

    if (work == null)
        continue;

    Console.WriteLine($"Got work: {work.Id}");
    Console.WriteLine($"  Position: {work.Work.InitialFen}");
    Console.WriteLine($"  Moves: {string.Join(" ", work.Work.Moves ?? [])}");

    // Run your engine and stream UCI output
    var uciOutput = RunEngine(work.Work);
    await client.ExternalEngine.SubmitWorkAsync(work.Id, uciOutput, cancellationToken);
}

async IAsyncEnumerable<string> RunEngine(EngineWorkDetails work)
{
    // Start your UCI engine and send commands
    // Example output:
    yield return "info depth 10 score cp 25 pv e2e4 e7e5";
    yield return "info depth 15 score cp 30 pv e2e4 e7e5 g1f3";
    yield return "bestmove e2e4";
}

Options

ExternalEngineRegistration

Property Type Description
Name string Display name (3-200 chars)
MaxThreads int Max threads available (1-65536)
MaxHash int Max hash table size in MiB (1-1048576)
ProviderSecret string Secret for provider auth (min 16 chars)
Variants IReadOnlyList<string>? Supported variants
ProviderData string? Arbitrary provider data

EngineAnalysisWork

Property Type Description
SessionId string? Session identifier
Threads int? Threads to use
Hash int? Hash table size (MiB)
MultiPv int? Principal variations (1-5)
Variant string? Chess variant
InitialFen string Starting FEN
Moves IReadOnlyList<string>? UCI moves from initial position
InfiniteDepth int? Depth limit
Nodes long? Node limit
MoveTime int? Time limit (ms)

Response Types

ExternalEngine

Property Type Description
Id string Engine registration ID
Name string Display name
ClientSecret string Secret for analysis requests
UserId string Owner's user ID
MaxThreads int Max available threads
MaxHash int Max hash size (MiB)
Variants IReadOnlyList<string> Supported variants
ProviderData string? Provider data

EngineAnalysisLine

Property Type Description
Time int? Analysis time (ms)
Depth int? Search depth
SelDepth int? Selective depth
Nodes long? Nodes searched
Pv string? Principal variation
Score EngineScore? Evaluation
CurrentMove string? Move being searched
CurrentMoveNumber int? Current move number
HashFull int? Hash table usage (per mille)
Nps long? Nodes per second
TbHits long? Tablebase hits
MultiPv int? PV line number (1-indexed)

EngineScore

Property Type Description
Centipawns int? Score in centipawns
Mate int? Mate in N moves

See Also

Clone this wiki locally