elsa-workflows/elsa-core · error · InvalidOperationException

Elsa Platform endpoint is required.

Error message

Elsa Platform endpoint is required.

What it means

BuildUri resolves request paths against _options.PlatformEndpoint. If PlatformEndpoint is null, it throws this InvalidOperationException because no request can be addressed. Absolute paths like artifact DownloadUrls bypass the endpoint, but the endpoint itself is always mandatory.

Solutions

  1. Set PlatformEndpoint to the absolute Platform base URL, e.g. https://platform.example.com.
  2. Ensure options binding/validation runs at startup so this surfaces as a clear config error instead of a runtime failure.
  3. Fix the missing configuration key in appsettings/environment variables for the deployment.

Example fix

// before
var client = new PlatformRuntimeCommandClient(httpClient, new ElsaPlatformIntegrationOptions { EngineId = id });
// after
var options = new ElsaPlatformIntegrationOptions { PlatformEndpoint = new Uri("https://platform.example.com"), EngineId = id, WorkspaceId = wsId };
options.Validate();
var client = new PlatformRuntimeCommandClient(httpClient, options);
Defensive patterns

Strategy: validation

Validate before calling

if (options.Enabled && string.IsNullOrWhiteSpace(options.PlatformEndpoint?.ToString()))
    throw new InvalidOperationException("PlatformEndpoint must be configured before using the command client.");

Type guard

bool HasEndpoint(ElsaPlatformIntegrationOptions o) => !string.IsNullOrWhiteSpace(o.PlatformEndpoint?.AbsoluteUri);

Try / catch

try { var commands = await client.PollAsync(ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("endpoint is required"))
{
    logger.LogError(ex, "PlatformEndpoint is not configured.");
}

Prevention

When it happens

Trigger: Any PlatformRuntimeCommandClient call (PollAsync, complete/report, DownloadArtifactAsync) when _options.PlatformEndpoint was never assigned — typically because Validate() was never run or options were constructed in code without the endpoint.

Common situations: Registering the client manually without going through the feature's options pipeline that validates the endpoint; configuration key Elsa:PlatformIntegration:PlatformEndpoint missing in the environment; tests newing up options with only EngineId set.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/063ea93afb37350b. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Platform.Integration/Services/PlatformRuntimeCommandClient.cs:115

    private HttpRequestMessage CreateRequest(HttpMethod method, Uri uri)
    {
        var request = new HttpRequestMessage(method, uri);
        if (!string.IsNullOrWhiteSpace(_options.EngineSecret))
            request.Headers.TryAddWithoutValidation(EngineSecretHeaderName, _options.EngineSecret);
        return request;
    }

    private async Task<HttpResponseMessage> SendJsonAsync<TRequest>(Uri uri, TRequest body, CancellationToken cancellationToken)
    {
        var request = CreateRequest(HttpMethod.Post, uri);
        request.Content = JsonContent.Create(body, options: JsonOptions);
        return await httpClient.SendAsync(request, cancellationToken);
    }

    private Uri BuildUri(string path)
    {
        var endpoint = _options.PlatformEndpoint ?? throw new InvalidOperationException("Elsa Platform endpoint is required.");
        if (Uri.TryCreate(path, UriKind.Absolute, out var absoluteUri))
            return absoluteUri;

        var relative = path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)
            ? path
            : $"/api/workspaces/{_options.WorkspaceId:D}{path}";
        return new Uri($"{endpoint.AbsoluteUri.TrimEnd('/')}{relative}");
    }

    private async Task CopyBoundedAsync(Stream source, Stream destination, CancellationToken cancellationToken)
    {
        var buffer = new byte[81920];
        long total = 0;
        while (true)
        {
            var read = await source.ReadAsync(buffer, cancellationToken);
            if (read == 0)
                return;

View on GitHub (pinned to fe9217bdfa)