Appearance
C# Scripts
C# scripts run via Roslyn — Microsoft's in-process C# compiler. Unlike the other languages which spawn a subprocess, C# scripts are compiled and executed directly within the ApiMeld process, giving them full access to the .NET runtime, async/await, LINQ, and strong typing with no shell overhead.
Key differences from other languages
| C# | PowerShell / JS / Python / Bash | |
|---|---|---|
| Runtime | Roslyn (in-process) | Subprocess (pwsh / node / python3 / bash) |
| Typing | Strong, static | Dynamic / loosely typed |
| Async | Full async/await | Varies |
| Entry point | Top-level statements | Top-level statements |
Injected variables
The following variables are available at the top level of every C# script — no imports or declarations needed:
| Variable | Type | Description |
|---|---|---|
Logger | CSharpScriptLogger | Structured log output |
Datasources | dynamic | Named data source connections |
AuthHeaders | Dictionary<string, string> | Pre-built auth headers (if configured) |
Notifications | dynamic | Send Slack, Teams, or email notifications |
Payload | string? | Webhook request body (webhook-triggered runs) |
MqttTopic | string? | MQTT topic that triggered the run |
MqttPayload | string? | MQTT message payload |
Logging
csharp
Logger.Info("Starting sync");
Logger.Warn("Rate limit approaching — slowing down");
Logger.Error("Connection failed: " + ex.Message);
Logger.Debug("Response body: " + json);Log entries appear colour-coded in the Run History viewer. Logger.Error() and Logger.Warn() cause the run to finish with a CompletedWithWarnings status if no exception is thrown.
Basic example
csharp
using System.Net.Http;
using System.Text.Json;
Logger.Info("Fetching data");
var client = new HttpClient();
var response = await client.GetStringAsync("https://api.example.com/data");
var doc = JsonDocument.Parse(response);
var count = doc.RootElement.GetProperty("items").GetArrayLength();
Logger.Info($"Received {count} items");Querying a database
Data source connections are accessed via the Datasources dynamic object. The name matches the data source name configured in ApiMeld.
csharp
// Query returns IEnumerable<dynamic>
var rows = await Datasources.ProductionDB.QueryAsync(
"SELECT id, name, email FROM users WHERE active = @active",
new { active = true }
);
foreach (var row in rows)
{
Logger.Info($"User: {row.name} <{row.email}>");
}Parameterised queries use named @param placeholders regardless of the underlying database type — ApiMeld maps them appropriately.
Calling a REST API data source
csharp
// Call an endpoint defined in the data source's OpenAPI spec
var forecast = await Datasources.WeatherApi.GET_V1_FORECAST(new
{
latitude = "51.5",
longitude = "-0.1"
});
Logger.Info($"Temperature: {forecast.current.temperature_2m}");Or call any endpoint directly:
csharp
var result = await Datasources.WeatherApi.GetAsync("/v1/forecast?latitude=51.5&longitude=-0.1");Reading from a network share or SFTP
csharp
// Read a CSV from a network share
var rows = await Datasources.ReportsShare.ReadCsvAsync("Incoming/sales.csv");
Logger.Info($"Rows: {rows.Count()}");
// Upload a result file to SFTP
await Datasources.VendorSftp.UploadAsync("Outgoing/summary.txt", $"Total: {rows.Count()}");Using auth headers
If the task has an auth configuration, the pre-built headers are available as AuthHeaders:
csharp
var client = new HttpClient();
foreach (var h in AuthHeaders)
client.DefaultRequestHeaders.Add(h.Key, h.Value);
var response = await client.GetStringAsync("https://api.example.com/secure");Sending notifications
csharp
await Notifications.Slack("deployments").SendAsync("Deploy completed successfully");
await Notifications.Teams("ops-alerts").SendAsync("Disk usage exceeded 90%");
await Notifications.Email("admin@example.com", "Alert", "Disk usage exceeded 90%");Webhook and MQTT context
csharp
// Webhook-triggered run
if (Payload != null)
{
var data = JsonDocument.Parse(Payload);
var version = data.RootElement.GetProperty("version").GetString();
Logger.Info($"Deploying version: {version}");
}
// MQTT-triggered run
if (MqttPayload != null)
{
Logger.Info($"Message on {MqttTopic}: {MqttPayload}");
}Sandbox restrictions
The Roslyn executor validates scripts with an AST (syntax tree) analyser before running. The following are blocked regardless of sandbox mode:
System.Reflection— prevents runtime type inspection and privilege escalationSystem.Diagnostics.Process— prevents spawning child processesSystem.IOfile operations — use data source file methods insteadunsafecode blocks- P/Invoke and native interop
Safe APIs are available by default: LINQ, System.Text.Json, System.Net.Http.HttpClient, regex, string manipulation, and math.
Unrestricted mode
Admins can enable unrestricted mode per role (Admin → Settings → Access Restrictions), which bypasses the AST validator. OS-level isolation via the script-runner user remains active regardless.
Supported using namespaces
Common namespaces are pre-imported — you do not need to add using statements for:
SystemSystem.Collections.GenericSystem.LinqSystem.TextSystem.Text.JsonSystem.Net.Http
Add explicit using statements for anything else you need.