diff --git a/README.md b/README.md
index 2aaca34..dfade1d 100644
--- a/README.md
+++ b/README.md
@@ -1000,6 +1000,34 @@ provider (the payload is the project's data) and when the function was not
called. There is no default Code integration: every call names the
integration it means (`IntegrationId`), as the examples above do.
+## Marketplace function runs
+
+Each call of a marketplace function is one run row: an invoke (user, API
+key, AI), a trigger run or a scheduler fire.
+`Code.GetMarketplaceFunctionRunsAsync` lists them — set exactly one of
+`FunctionId` (every source, newest first, up to 50), `TriggerId` or
+`TaskId`. A row carries `Status` (`ok` / `failed` / `skipped`, `null` on
+rows written before the field existed), `DurationMs`, `Source`, `Event`,
+`CorrelationId` and `HasDetails`.
+
+The input, steps and response of a run are written to the project log under
+the row's `CorrelationId`, only while the environment's Norbix Logging is on
+— then `HasDetails` is `true`. `GetMarketplaceFunctionRunDetailsAsync` (in
+`Norbix.Hub`, a hand-written helper over `Logs.GetLogsByCorrelationIdAsync`,
+`GET /{version}/logs/audit?targetCorrelationId=…`) reads them:
+
+```csharp
+var runs = await hub.Code.GetMarketplaceFunctionRunsAsync(
+ new GetMarketplaceFunctionRuns { FunctionId = "func_…", Limit = 10 });
+
+foreach (var run in runs?.Runs ?? [])
+{
+ if (!run.HasDetails || run.CorrelationId is null) continue; // logs were off
+ var details = await hub.GetMarketplaceFunctionRunDetailsAsync(run.CorrelationId);
+ // details.Items: marketplace:run:started → marketplace:run:step … → completed | failed | skipped
+}
+```
+
## Coverage Notes
Generated coverage tracks the API and Hub DTO contract files. Some flows are not generated automatically:
diff --git a/src/Norbix.Hub/NorbixHubClient.Convenience.cs b/src/Norbix.Hub/NorbixHubClient.Convenience.cs
index e74be0b..8d094d9 100644
--- a/src/Norbix.Hub/NorbixHubClient.Convenience.cs
+++ b/src/Norbix.Hub/NorbixHubClient.Convenience.cs
@@ -23,5 +23,25 @@ public sealed partial class NorbixHubClient
},
cancellationToken);
}
-}
+ ///
+ /// The details of one marketplace function run: the project log lines filed
+ /// under the run's correlation id (marketplace:run:started, step,
+ /// then completed, failed or skipped). Pass
+ /// of a row from
+ /// Code.GetMarketplaceFunctionRunsAsync. Only a row with
+ /// has lines — they are
+ /// written only while the environment's Norbix Logging is on.
+ /// Same call as Logs.GetLogsByCorrelationIdAsync
+ /// (GET /{version}/logs/audit?targetCorrelationId=…).
+ ///
+ public Task GetMarketplaceFunctionRunDetailsAsync(
+ string correlationId,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
+ return Logs.GetLogsByCorrelationIdAsync(
+ new GetLogsByCorrelationId { TargetCorrelationId = correlationId },
+ cancellationToken);
+ }
+}
diff --git a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs
index f9027d3..0e921cc 100644
--- a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs
+++ b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs
@@ -3901,6 +3901,9 @@ public partial class MarketplaceFunctionRunDto
[DataMember]
public virtual bool Succeeded { get; set; }
+ [DataMember]
+ public virtual string? Status { get; set; }
+
[DataMember]
public virtual long? DurationMs { get; set; }
@@ -3910,6 +3913,18 @@ public partial class MarketplaceFunctionRunDto
[DataMember]
public virtual string? VendorRequestId { get; set; }
+ [DataMember]
+ public virtual string? CorrelationId { get; set; }
+
+ [DataMember]
+ public virtual bool HasDetails { get; set; }
+
+ [DataMember]
+ public virtual string? TriggerId { get; set; }
+
+ [DataMember]
+ public virtual string? TaskId { get; set; }
+
[DataMember]
public virtual string? Input { get; set; }
}
@@ -11422,12 +11437,17 @@ public partial class GetMarketplaceFunctionRuns
: CodeMashRequestBase, INorbixRequest
{
///
- ///The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId.
+ ///The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId.
+ ///
+ public virtual string? FunctionId { get; set; }
+
+ ///
+ ///The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId.
///
public virtual string? TriggerId { get; set; }
///
- ///The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId.
+ ///The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId.
///
public virtual string? TaskId { get; set; }
diff --git a/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs
new file mode 100644
index 0000000..b2239ec
--- /dev/null
+++ b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs
@@ -0,0 +1,87 @@
+using Norbix.Sdk;
+using Norbix.Sdk.Tests.Helpers;
+using Norbix.Sdk.Types.Hub;
+
+using NUnit.Framework;
+
+using VerifyNUnit;
+
+namespace Norbix.Hub.Tests;
+
+///
+/// GET /{version}/code/marketplace/functions/runs takes exactly one of
+/// functionId / triggerId / taskId. The coverage snapshot fills every field at
+/// once; these cases pin a function's own runs (functionId alone, on the query,
+/// no body) and the details helper over GET /{version}/logs/audit.
+/// Nothing leaves the process.
+///
+[TestFixture]
+public sealed class MarketplaceRunsTests
+{
+ private const string CorrelationId = "0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012";
+
+ private static NorbixTestFixture NewFixture() =>
+ NorbixTestFixture.Create(o =>
+ {
+ o.AccountId = "test-account";
+ o.BearerToken = "test-bearer";
+ });
+
+ [Test]
+ public async Task Function_runs_send_functionId_and_limit_on_the_query_with_no_body()
+ {
+ using var fixture = NewFixture();
+ fixture.RespondNext(new
+ {
+ runs = new[]
+ {
+ new
+ {
+ functionId = "func_1p4R3gTobGZpD6fXvHT4rd",
+ source = "api-key",
+ @event = "invoke",
+ succeeded = true,
+ status = "ok",
+ correlationId = CorrelationId,
+ hasDetails = true,
+ },
+ },
+ });
+
+ var response = await fixture.Client.Code.GetMarketplaceFunctionRunsAsync(
+ new GetMarketplaceFunctionRuns { FunctionId = "func_1p4R3gTobGZpD6fXvHT4rd", Limit = 50 });
+
+ // The snapshot pins verb, path, the exact query (functionId + limit only) and no body,
+ // and the new row fields read back from the generated response.
+ await Verifier.Verify(new { Sent = fixture.LastRequest, response!.Runs }, VerifyConfig.VerifySettings);
+ }
+
+ [Test]
+ public async Task Run_details_read_the_log_lines_by_targetCorrelationId()
+ {
+ using var fixture = NewFixture();
+
+ await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(CorrelationId);
+
+ // GET /v2/logs/audit?targetCorrelationId=… — the DTO property name, not "correlationId".
+ await Verifier.Verify(fixture.LastRequest, VerifyConfig.VerifySettings);
+ }
+
+ [Test]
+ public async Task Run_details_refuse_an_empty_correlation_id()
+ {
+ using var fixture = NewFixture();
+
+ string? error = null;
+ try
+ {
+ await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(" ");
+ }
+ catch (ArgumentException ex)
+ {
+ error = $"{ex.GetType().Name}: {ex.ParamName}";
+ }
+
+ await Verifier.Verify(new { Error = error, NothingSent = fixture.RecordedRequests.Count == 0 }, VerifyConfig.VerifySettings);
+ }
+}
diff --git a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt
index 9fd9f75..d18ee68 100644
--- a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt
+++ b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt
@@ -700,7 +700,7 @@
Sent: {
Method: GET,
Path: /v2/code/marketplace/functions/runs,
- Query: ?triggerId={guid}&taskId={guid}&limit=1&env=test-env,
+ Query: ?functionId={guid}&triggerId={guid}&taskId={guid}&limit=1&env=test-env,
Headers: {
Authorization: Bearer test-bearer,
norbix-account-id: test-account,
@@ -711,7 +711,8 @@
Response: {
Runs: [
{
- Succeeded: false
+ Succeeded: false,
+ HasDetails: false
}
],
ResponseStatus: {
diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt
new file mode 100644
index 0000000..14d4f35
--- /dev/null
+++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt
@@ -0,0 +1,23 @@
+{
+ Sent: {
+ Method: GET,
+ Path: /v2/code/marketplace/functions/runs,
+ Query: ?functionId=func_1p4R3gTobGZpD6fXvHT4rd&limit=50,
+ Headers: {
+ Authorization: Bearer test-bearer,
+ norbix-account-id: test-account,
+ norbix-project-id: test-project
+ }
+ },
+ Runs: [
+ {
+ Event: invoke,
+ Source: api-key,
+ FunctionId: func_1p4R3gTobGZpD6fXvHT4rd,
+ Succeeded: true,
+ Status: ok,
+ CorrelationId: Guid_1,
+ HasDetails: true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt
new file mode 100644
index 0000000..cf664ce
--- /dev/null
+++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt
@@ -0,0 +1,10 @@
+{
+ Method: GET,
+ Path: /v2/logs/audit,
+ Query: ?targetCorrelationId=0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012,
+ Headers: {
+ Authorization: Bearer test-bearer,
+ norbix-account-id: test-account,
+ norbix-project-id: test-project
+ }
+}
\ No newline at end of file
diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt
new file mode 100644
index 0000000..4489feb
--- /dev/null
+++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt
@@ -0,0 +1,4 @@
+{
+ Error: ArgumentException: correlationId,
+ NothingSent: true
+}
\ No newline at end of file