- 12 minutes to read

Logging using the REST Log API

Your own applications, scripts, and integration platforms can send Log Events straight to the Nodinite Log API. Each REST call posts one Log Event, or a batch, as JSON, and Nodinite stores it in the Log Databases where your Log Views find it.

This page is the starting point for logging from code. It explains the base URL, how the Log API is protected, how to explore the operations in Scalar or Swagger, and gives samples in PowerShell, curl, C#, Python, and Node.js.

  • ✅ Post single Log Events or batches over REST with a JSON payload
  • ✅ Validate a Log Event before you send it
  • ✅ Protect the Log API with OAuth 2.0 client credentials when you need to
  • ✅ Explore and test every operation in Scalar or Swagger

Tip

For high volumes, or when the Log API must not slow down your application, write the Log Events to a folder or queue and let the Pickup Log Events Service deliver them. It retries automatically.

Understanding Logging over REST

graph LR App["fa:fa-code Your application
or script"] IdP["fa:fa-id-card Identity provider
only when protected"] API["fa:fa-cloud-arrow-down Log API
/LogAPI/api/LogEvent"] DB["fa:fa-database Log Databases"] LV["fa:fa-search Log Views"] App -->|1. Get token| IdP App -->|2. POST JSON Log Event| API API --> DB DB --> LV style API fill:#2678BD,color:#fff style IdP fill:#9E59BB,color:#fff style DB fill:#3F827B,color:#fff

Your application posts JSON Log Events to the Log API (blue). When the Log API is protected, the application first gets an access token from the identity provider (purple). The events are stored in the Log Databases (green) and shown in Log Views.

Key Components

Component Role
Log API Receives Log Events over REST and stores them. Each environment has its own Log API web site and port.
JSON Log Event The payload. See JSON formatted Log Event for every field.
Protection Off by default: the Log API uses Anonymous Authentication. When you turn on Enable Authentication in the Portal, every request needs an OAuth 2.0 bearer token.
Scalar and Swagger Interactive API references at the root of the Log API web site. Use them to find operations, see the models, and test calls.

Find the Base URL

In Nodinite v7, each environment's Log API is its own IIS web site with its own port. The Log API operations are under /LogAPI/ on that site. The port is set for each environment in the Nodinite Portal, under Core platform services in the Servers step, so yours probably differs from the examples.

What URL Example
Log API base URL https://{host}:{port}/LogAPI/ https://nodinite.contoso.com:40002/LogAPI/
Post a Log Event <base URL>api/LogEvent https://nodinite.contoso.com:40002/LogAPI/api/LogEvent
Post a batch <base URL>api/LogEvent/Batch https://nodinite.contoso.com:40002/LogAPI/api/LogEvent/Batch
Validate without storing <base URL>api/LogEvent/Validate and api/LogEvent/Batch/Validate https://nodinite.contoso.com:40002/LogAPI/api/LogEvent/Validate
Scalar (recommended) https://{host}:{port}/scalar https://nodinite.contoso.com:40002/scalar
Swagger https://{host}:{port}/swagger https://nodinite.contoso.com:40002/swagger

Note

Log agents and the Pickup Service ask for the Log API base URL, ending in /LogAPI/. Scalar and Swagger are at the root of the site, without /LogAPI/. Always use HTTPS in production.

Explore Operations in Scalar or Swagger

Both API references list every operation with its request and response models, and let you send test requests. Scalar also generates code snippets in many languages; add authentication yourself when the Log API is protected. Swagger may be removed in a future version, so prefer Scalar.

Scalar showing POST /LogAPI/api/LogEvent with a C# HttpClient snippet and example response (7.10)
Scalar shows the Log Event operation with a sample payload, a generated code snippet, and the response for each status code.

Swagger showing POST /LogAPI/api/LogEvent with an editable sample request body (7.10)
In Swagger, Try it out fills in a sample Log Event that you can edit and send.

Choose How to Protect the Log API

Unprotected (default) Protected with OAuth 2.0
Portal setting Enable Authentication cleared Enable Authentication checked, with the Log API Audiences
What the caller sends Only the JSON payload Authorization: Bearer <access token> plus the payload
How the caller gets access Network access to the Log API A client app registration using the OAuth 2.0 client credentials grant
Pickup Service setting Protected cleared Protected checked
Requires Nothing Nodinite running in OpenID Connect / OAuth 2.0 mode
Use when The Log API is only reachable from trusted networks Callers run outside your trusted network, or your policy requires authenticated logging

You set both values in the Nodinite Portal, in Step 2: Auth › Log API; the fields are described in OAuth Log API settings.

Set Up a Client for a Protected Log API

The Log API has its own app registration and audience, separate from the Web API. In short:

  1. Register the Log API (logApi) in Microsoft Entra ID, set its Application ID URI, and set its access token version to 2.
  2. Register one client app per system that sends Log Events.
  3. In the Portal, add the logApi Application (client) ID (not the api:// URI) to LogAPI.2 – Audiences, and check LogAPI.1 – Enable Authentication.
  4. The logging client requests a token with the scope api://<logapi-client-id>/.default and sends it as a bearer token.
  5. In production, harden the setup: add the AppRole_LogApi_write app role, grant it to each approved client, and set Assignment required on logApi. Without this, any app registration in your tenant can get a Log API token.

Follow the step-by-step guide with screenshots: Protect the Log API with OAuth 2.0 and Entra ID.

Warning

Don't use the installation client (NodiniteInstallationClient) for your own logging. Give each calling system its own client, so you can rotate or revoke it on its own.

JSON Payload and Response

Post Log Events as JSON formatted Log Events. At minimum, provide the mandatory fields:

{
  "LogAgentValueId": 42,
  "EndPointName": "INT101: My Integration",
  "EndPointUri": "https://api.example.com/orders",
  "EndPointDirection": 0,
  "EndPointTypeId": 50,
  "OriginalMessageTypeName": "Orders.Customer/1.0",
  "LogDateTime": "2026-10-11T13:37:00.123Z"
}

A successful call returns 200 with the result for the Log Event:

{
  "EventId": "123e4567-e89b-12d3-a456-426614174000",
  "EventNumber": 1,
  "Status": 0,
  "ErrorMessage": null
}

A 400 response means the payload is invalid. Use api/LogEvent/Validate while you develop, to check a payload without storing it. For valid values, see Endpoint Directions, Endpoint Types, and Event Directions.

Code Examples

The examples use these placeholders:

Placeholder Value
<logapi-base-url> The Log API base URL, for example https://nodinite.contoso.com:40002/LogAPI/
<tenant-id>, <client-id>, <client-secret> Your Entra ID tenant and the client app, only when the Log API is protected
<logapi-client-id> The Application (client) ID of the logApi app registration, only when the Log API is protected

PowerShell

Use Invoke-RestMethod to post the Log Event. Remove the token lines and the -Headers parameter when the Log API is unprotected.

$logApiBaseUrl = '<logapi-base-url>'

# Only when the Log API is protected: get an access token
$token = (Invoke-RestMethod -Method Post `
    -Uri 'https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token' `
    -Body @{
        grant_type    = 'client_credentials'
        client_id     = '<client-id>'
        client_secret = $env:NODINITE_CLIENT_SECRET
        scope         = 'api://<logapi-client-id>/.default'
    }).access_token
$headers = @{ Authorization = "Bearer $token" }

$logEvent = @{
    LogAgentValueId         = 42
    EndPointName            = 'INT101: REST PowerShell Integration'
    EndPointUri             = 'https://api.example.com/orders'
    EndPointDirection       = 0
    EndPointTypeId          = 50
    OriginalMessageTypeName = 'Orders.Customer/1.0'
    LogDateTime             = (Get-Date).ToUniversalTime().ToString('o')
    LogStatus               = 0
    LogText                 = 'Order processed successfully'
    Body                    = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('<Order>...</Order>'))
    Context                 = @{ OrderId = 'ORD-12345'; CustomerId = 'CUST-001' }
}

$response = Invoke-RestMethod -Method Post -Uri "${logApiBaseUrl}api/LogEvent" `
    -Headers $headers -ContentType 'application/json' `
    -Body ($logEvent | ConvertTo-Json -Depth 10)

"Logged event $($response.EventId), status $($response.Status)"

Example: Post a Log Event from PowerShell.

curl

# Only when the Log API is protected
TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
  -d "grant_type=client_credentials" -d "client_id=<client-id>" \
  -d "client_secret=$NODINITE_CLIENT_SECRET" -d "scope=api://<logapi-client-id>/.default" | jq -r .access_token)

curl -X POST "<logapi-base-url>api/LogEvent" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"LogAgentValueId":42,"EndPointName":"INT101: REST curl Integration","EndPointUri":"https://api.example.com/orders","EndPointDirection":0,"EndPointTypeId":50,"OriginalMessageTypeName":"Orders.Customer/1.0","LogDateTime":"2026-10-11T13:37:00.123Z"}'

Example: Post a Log Event with curl. Leave out the Authorization header when the Log API is unprotected.

C#

When the Log API is protected, get the token with MSAL as in the Web API OAuth guide, using the scope api://<logapi-client-id>/.default, and set it on the HttpClient.

using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;

using var http = new HttpClient { BaseAddress = new Uri("<logapi-base-url>") };
// Only when the Log API is protected:
// http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

var logEvent = new
{
    LogAgentValueId = 42,
    EndPointName = "INT101: REST C# Integration",
    EndPointUri = "https://api.example.com/orders",
    EndPointDirection = 0,
    EndPointTypeId = 50,
    OriginalMessageTypeName = "Orders.Customer/1.0",
    LogDateTime = DateTimeOffset.UtcNow,
    LogStatus = 0,
    LogText = "Order processed",
    Body = Convert.ToBase64String(Encoding.UTF8.GetBytes("<Order>...</Order>")),
    Context = new Dictionary<string, string> { ["OrderId"] = "ORD-12345", ["CustomerId"] = "CUST-001" }
};

var response = await http.PostAsJsonAsync("api/LogEvent", logEvent);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());

Example: Post a Log Event from .NET with HttpClient.

Tip

For .NET, you can also use the Nodinite.Libraries.Contracts.LogRestApi NuGet package for typed Log Event contracts. See the Hello World .NET sample.

Python

Install the dependency first: pip install requests.

import base64
import datetime
import os
import requests

log_api_base_url = "<logapi-base-url>"
headers = {}

# Only when the Log API is protected
if os.environ.get("NODINITE_CLIENT_SECRET"):
    token = requests.post(
        "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
        data={
            "grant_type": "client_credentials",
            "client_id": "<client-id>",
            "client_secret": os.environ["NODINITE_CLIENT_SECRET"],
            "scope": "api://<logapi-client-id>/.default",
        },
        timeout=30,
    ).json()["access_token"]
    headers["Authorization"] = f"Bearer {token}"

log_event = {
    "LogAgentValueId": 42,
    "EndPointName": "INT101: REST Python Integration",
    "EndPointUri": "https://api.example.com/orders",
    "EndPointDirection": 0,
    "EndPointTypeId": 50,
    "OriginalMessageTypeName": "Orders.Customer/1.0",
    "LogDateTime": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    "Body": base64.b64encode(b"<Order>...</Order>").decode(),
    "Context": {"OrderId": "ORD-12345"},
}

response = requests.post(f"{log_api_base_url}api/LogEvent", json=log_event, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())

Example: Post a Log Event from Python.

Node.js

Requires Node.js 18 or later (built-in fetch).

const logApiBaseUrl = '<logapi-base-url>';
const headers = { 'Content-Type': 'application/json' };

// Only when the Log API is protected
if (process.env.NODINITE_CLIENT_SECRET) {
  const tokenResponse = await fetch('https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: '<client-id>',
      client_secret: process.env.NODINITE_CLIENT_SECRET,
      scope: 'api://<logapi-client-id>/.default'
    })
  });
  headers.Authorization = `Bearer ${(await tokenResponse.json()).access_token}`;
}

const response = await fetch(`${logApiBaseUrl}api/LogEvent`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    LogAgentValueId: 42,
    EndPointName: 'INT101: REST Node.js Integration',
    EndPointUri: 'https://api.example.com/orders',
    EndPointDirection: 0,
    EndPointTypeId: 50,
    OriginalMessageTypeName: 'Orders.Customer/1.0',
    LogDateTime: new Date().toISOString(),
    Body: Buffer.from('<Order>...</Order>').toString('base64'),
    Context: { OrderId: 'ORD-12345' }
  })
});
console.log(response.status, await response.json());

Example: Post a Log Event from Node.js.

Important

Read client secrets from a vault or environment variable at run time. Never commit secrets to source control, and never log tokens.

Best Practices

Use Asynchronous Logging for Volume

For high-volume scenarios, or when network reliability is a concern, use the Pickup Log Events Service instead of synchronous REST calls:

  1. Write JSON Log Events to a file system folder or message queue.
  2. Configure the Pickup Service to read from that location.
  3. Let Nodinite handle retries and processing.

This decouples your application from the logging infrastructure and improves resilience; see Asynchronous Logging. To send many events at once over REST, post them to api/LogEvent/Batch.

Include Context Properties

Use the Context field to add searchable business data:

"Context": {
  "OrderId": "ORD-12345",
  "CustomerId": "CUST-001",
  "TotalAmount": "1234.56",
  "CorrelationId": "064205E2-F7CF-43A6-B514-4B55536C2B67"
}

This enables self-service Log Views for business users without payload parsing. See Context Options for the reserved keys that change how an event is processed, and Repository Binding for the keys that populate the Repository Model. Context properties that match the ContextPropertiesFilters System Parameter are removed before they are stored; see What happens after the Log API accepts an event.

Handle Errors Gracefully

Always implement error handling and retry logic, and never let a logging failure stop your business process:

try {
    Invoke-RestMethod -Method Post -Uri "${logApiBaseUrl}api/LogEvent" -Headers $headers `
        -ContentType 'application/json' -Body $jsonBody
} catch {
    Write-Warning "Failed to log event: $($_.Exception.Message)"
    # Retry, or write the event to a folder for the Pickup Service
}

Base64 Encode Payloads

Always base64 encode message bodies:

$body = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($xmlMessage))

Next Steps