A .NET 10 client library for the Fiix CMMS API, ported from the official Java client. Supports full CRUD, RPC, and Batch operations with async/await throughout.
- Overview
- Project Structure
- Local Development (No Fiix Account Needed)
- Installation
- Configuration
- Quick Start
- CRUD Operations
- RPC Methods
- Batch Operations
- Error Handling
- Field Naming Convention
- Display Values
- Design Philosophy
- CLI Testing Tool
- API Compatibility Notes
- Community & Support
- Resources
- License
API Version: 2.48.1
Target Framework: .NET 10 (net10.0)
Serialization: System.Text.Json
Authentication: HMAC SHA256
| API Family | Coverage | Operations |
|---|---|---|
| CRUD | ✅ 100% | Add, Find, FindById, Change, Remove |
| RPC | ✅ 100% | All 18 documented methods |
| Batch | ✅ 100% | Atomic multi-operation execution |
FiixCmms.Client.Net/
├── FiixCmms.Client/ # Core library
│ ├── Api/
│ │ ├── Batch/ # Batch request/response types
│ │ ├── Crud/ # CRUD request/response types
│ │ │ ├── FindFilter.cs # Query language filter
│ │ │ ├── FindRequest.cs / FindResponse.cs
│ │ │ ├── FindByIdRequest.cs / FindByIdResponse.cs
│ │ │ ├── AddRequest.cs / AddResponse.cs
│ │ │ ├── ChangeRequest.cs / ChangeResponse.cs
│ │ │ └── RemoveRequest.cs / RemoveResponse.cs
│ │ ├── Rpc/ # RPC request/response types
│ │ ├── Request.cs / Response.cs # Base classes
│ │ └── ApiError.cs
│ ├── Format/
│ │ └── JsonFormat.cs # JSON serialization with Fiix naming policy
│ ├── Interfaces/
│ │ ├── ICredentials.cs
│ │ ├── IFormat.cs
│ │ └── ITransport.cs
│ ├── Models/ # 107 DTO classes
│ │ ├── ClientCmmsDto.cs # Base DTO with ExtraFields support
│ │ ├── Asset.cs
│ │ ├── WorkOrder.cs
│ │ ├── User.cs
│ │ └── ... (104 more)
│ ├── Transport/
│ │ └── HttpTransport.cs
│ ├── BasicCredentials.cs
│ └── FiixCmmsClient.cs # Main client facade
│
├── FiixCmms.Client.Cli/ # CLI testing tool
│ ├── Examples/
│ │ ├── Rpc/
│ │ │ ├── AllRpcMethods.cs # Helpers for all 18 RPC methods
│ │ │ └── ComprehensiveRpcExamples.cs
│ │ ├── CrudExample.cs
│ │ ├── FindExamples.cs
│ │ ├── BatchExamples.cs
│ │ ├── RpcExamples.cs
│ │ ├── TestConnection.cs
│ │ └── DiagnosticsExample.cs
│ ├── Program.cs
│ ├── FiixCmmsSettings.cs
│ └── appsettings.json # Default: points at local mock API
│
└── FiixCmms.MockApi/ # Local mock API (no Fiix account needed)
├── Auth/
│ └── SignatureValidator.cs # HMAC-SHA256 request validation
├── Handlers/
│ ├── CrudHandler.cs # In-memory CRUD
│ ├── RpcHandler.cs # Mock RPC responses
│ └── BatchHandler.cs # Batch fan-out
├── Store/
│ └── InMemoryStore.cs # Seeded test data
├── appsettings.json # Mock credentials config
└── Program.cs # ASP.NET minimal API entrypoint
A fully functional mock API is included. It validates HMAC-SHA256 signatures and serves all CLI commands from an in-memory data store — no Fiix account or sandbox required.
Terminal 1 — start the mock API:
cd FiixCmms.MockApi
dotnet runListens on http://localhost:5100. The CLI's default appsettings.json already points here.
Terminal 2 — run CLI commands:
cd FiixCmms.Client.Cli
dotnet run -- test-auth # verify HMAC auth works
dotnet run -- crud # full create/update/find/delete cycle
dotnet run -- find Asset # list seeded assets
dotnet run -- rpc # ping, timezone, sites, custom-fields
dotnet run -- batch find # batch find across multiple entity types
dotnet run -- batch mixed # mixed RPC + CRUD in one request
dotnet run -- batch add 20 # transactional batch create
dotnet run -- help # all available commandsTo use a real Fiix instance instead, create
FiixCmms.Client.Cli/appsettings.local.jsonwith your credentials — it overrides the defaults automatically. SeeFiixCmms.Client.Cli/README.md.
dotnet add package FiixCmms.ClientOr search for FiixCmms.Client in the Visual Studio NuGet Package Manager.
Clone the repo and add a project reference:
dotnet add reference ../FiixCmms.Client/FiixCmms.Client.csprojCreate appsettings.local.json in the CLI project (or configure programmatically):
{
"FiixCmms": {
"BaseUri": "https://yourinstance.fiixsandbox.com/api/",
"AppKey": "your-app-key",
"AccessKey": "your-access-key",
"SecretKey": "your-secret-key"
}
}using FiixCmms.Client;
using FiixCmms.Client.Models;
var credentials = new BasicCredentials(
appKey: "your-app-key",
accessKey: "your-access-key",
secretKey: "your-secret-key"
);
var client = new FiixCmmsClient(
credentials,
baseUri: "https://yourinstance.fiixsandbox.com/api/"
);
// Find a work order by ID
var request = client.PrepareFindById<WorkOrder>();
request.Id = 12345;
request.Fields = "id, strCode, strDescription";
var response = await client.FindByIdAsync(request);
if (response.Error == null)
{
Console.WriteLine($"Work Order: {response.Object?.StrCode}");
}var request = client.PrepareFindById<Asset>();
request.Id = 12345;
request.Fields = "id, strName, strCode";
var response = await client.FindByIdAsync(request);
if (response.Error == null)
{
var asset = response.Object;
Console.WriteLine($"{asset.StrName} ({asset.StrCode})");
}Response: { "object": { ... } }
var request = client.PrepareFind<WorkOrder>();
request.Filters = new List<FindFilter>
{
new FindFilter
{
Ql = "intWorkOrderStatusID=? AND bolIsActive=?",
Parameters = new List<object> { 1, 1L }
}
};
request.Fields = "id, strCode, strDescription";
request.OrderBy = "id DESC";
request.Limit = 10;
var response = await client.FindAsync(request);
foreach (var wo in response.Objects ?? new List<WorkOrder>())
{
Console.WriteLine($"{wo.StrCode}: {wo.StrDescription}");
}Response: { "objects": [ ... ] }
The Ql property uses ? placeholders with Parameters:
// Equality
{ Ql = "intSiteID = ?", Parameters = new List<object> { 1 } }
// Multiple conditions
{ Ql = "intSiteID = ? AND bolIsActive = ?", Parameters = new List<object> { 1, 1L } }
// String match
{ Ql = "strName LIKE ?", Parameters = new List<object> { "%pump%" } }Supported Operators: =, !=, >, <, >=, <=, LIKE, AND, OR, IS NULL, IS NOT NULL
var request = client.PrepareAdd<Asset>();
request.Object = new Asset
{
StrName = "New Equipment",
StrCode = "EQ-001",
IntSiteID = 1
};
request.Fields = "id, strName, strCode";
var response = await client.AddAsync(request);
Console.WriteLine($"Created ID: {response.Object?.Id}");Response: { "object": { ... } }
var request = client.PrepareChange<Asset>();
request.Object = new Asset
{
Id = 12345,
StrName = "Updated Name",
StrDescription = "Updated Description"
};
request.ChangeFields = "strName, strDescription"; // Only these fields are written
request.Fields = "id, strName, strDescription"; // Fields returned in response
var response = await client.ChangeAsync(request);Important: Only fields listed in
ChangeFieldsare updated, even if other properties are set onObject.
Response: { "object": { ... } }
var request = client.PrepareRemove<Asset>();
request.Id = 12345;
var response = await client.RemoveAsync(request);
Console.WriteLine($"Removed {response.Count} record(s)");Response: { "success": true }
All 18 documented Fiix RPC methods are implemented.
var request = client.PrepareRpc();
request.Name = "Ping";
var response = await client.RpcAsync(request);
if (response.Error == null)
{
Console.WriteLine("Server is up!");
}var request = client.PrepareParameterizedRpc();
request.Name = "CustomFields";
request.Action = "getWorkOrderCustomFieldsMetaData";
// request.Parameters = new Dictionary<string, object> { ... };
var response = await client.RpcAsync<dynamic>(request);| Category | Method | Description |
|---|---|---|
| Connectivity | Ping |
Test server connectivity |
| Connectivity | GetTimezone |
Get server timezone |
| Connectivity | GetAccessibleSites |
Sites accessible to current user |
| Assets | AssetResolved |
Resolved asset info with related data |
| Calendar | Calendar |
Calendar information |
| Scheduling | ScheduleTriggerAssetEvent |
Asset event scheduling |
| Scheduling | ScheduleTriggerCommon |
Common schedule trigger |
| Scheduling | ScheduleTriggerMeterReading |
Meter reading trigger |
| Scheduling | ScheduleTriggerTime |
Time-based trigger |
| Work Orders | WorkOrderLog |
Work order log entries |
| Work Orders | FollowOnWorkOrders |
Follow-on work orders |
| Work Orders | TaskGroupsToWorkOrder |
Convert task groups to WOs |
| Custom Fields | CustomFields |
Custom field operations |
| Inventory | StocksReceived |
Received stock information |
| Audit | ActivityLog |
Activity log entries |
| Audit | AuditTrail |
Audit trail information |
| Reporting | DashboardWidget |
Dashboard widget data |
| Reporting | DataExport |
Data export |
// Basic RPC
public async Task<RpcResponse> RpcAsync(RpcRequest request, CancellationToken ct = default)
// Parameterized RPC
public async Task<ParameterizedRpcResponse<T>> RpcAsync<T>(ParameterizedRpcRequest request, CancellationToken ct = default) where T : class
// Paged RPC
public async Task<PagedResponse<T>> RpcPagedAsync<T>(RpcRequest request, CancellationToken ct = default) where T : classExecute multiple CRUD and/or RPC operations in a single HTTP request. The entire batch is transactional — if any operation fails, all are rolled back.
var batch = client.PrepareBatch();
// Add multiple operations
var assetReq = client.PrepareFind<Asset>();
assetReq.Fields = "id, strName";
assetReq.Limit = 10;
batch.Requests.Add(assetReq);
var pingReq = client.PrepareRpc();
pingReq.Name = "Ping";
batch.Requests.Add(pingReq);
var response = await client.BatchAsync(batch);
if (response.Error == null)
{
// response.Responses[0] = Asset find results
// response.Responses[1] = Ping result
Console.WriteLine($"Got {response.Responses.Count} responses");
}var batch = client.PrepareBatch();
for (int i = 1; i <= 3; i++)
{
var addReq = client.PrepareAdd<Asset>();
addReq.Object = new Asset { StrName = $"Asset {i}", IntSiteID = siteId };
addReq.Fields = "id, strName";
batch.Requests.Add(addReq);
}
// All three assets are created, or none are (transactional)
var response = await client.BatchAsync(batch);| Approach | Requests | Time (est.) |
|---|---|---|
| 4 individual calls | 4 | ~600ms |
| 1 batch call | 1 | ~200ms (~67% faster) |
- Recommended batch size: 10–50 operations
- Split unrelated operations into separate batches to limit rollback scope
- Track response ordering — responses match request order exactly
All responses include an Error property:
var response = await client.FindByIdAsync(request);
if (response.Error != null)
{
Console.WriteLine($"Error: {response.Error.Message}");
Console.WriteLine($"Code: {response.Error.Code}");
Console.WriteLine($"Leg: {response.Error.Leg}");
return;
}
// Safe to use response.Object hereThe Fiix API uses Hungarian notation. Properties in the .NET DTOs use PascalCase and are serialized back to their original casing via FiixPropertyNamingPolicy (lowercase first character, rest preserved):
| Prefix | Type | Example field | .NET Property |
|---|---|---|---|
int |
Integer | intSiteID |
IntSiteID |
str |
String | strName |
StrName |
dtt |
DateTime | dttDateCreated |
DttDateCreated |
bol |
Boolean | bolIsActive |
BolIsActive |
dbl |
Double | dblHourlyRate |
DblHourlyRate |
qty |
Quantity | qtyOnHand |
QtyOnHand |
Request dv_-prefixed fields to receive human-readable display values for foreign-key fields. They are returned in ExtraFields on the DTO:
request.Fields = "id, strName, intCategoryID, dv_intCategoryID";
// Response:
// asset.IntCategoryID => 5
// asset.ExtraFields["dv_intCategoryID"] => "Pumps"All network methods are async — there are no synchronous equivalents. This matches modern .NET libraries (HttpClient, Azure SDK, EF Core) and avoids deadlocks in UI and ASP.NET contexts.
// ✅ Correct
public async Task<List<Asset>> GetAssetsAsync()
{
var request = _client.PrepareFind<Asset>();
var response = await _client.FindAsync(request);
return response.Objects ?? new List<Asset>();
}
// ❌ Avoid — can deadlock
var response = _client.FindAsync(request).Result;For legacy synchronous contexts only:
var response = client.FindAsync(request).GetAwaiter().GetResult();See: Don't Block on Async Code
ITransport and IFormat are swappable for testing:
var transport = new HttpTransport(customHttpClient);
client.Transport = transport;The FiixCmms.Client.Cli project provides a command-line tool for exploring the API.
cd FiixCmms.Client.Cli
# Create appsettings.local.json with your credentials (see Configuration above)# Authentication
dotnet run -- test-auth
# CRUD
dotnet run -- crud # Complete CRUD workflow
dotnet run -- find Asset # Find all assets
dotnet run -- find Asset 12345 # Find asset by ID
dotnet run -- add # Add entity
dotnet run -- change # Update entity
dotnet run -- remove Asset 12345 # Delete entity
# RPC
dotnet run -- rpc # Basic RPC examples
dotnet run -- rpc ping # Test connectivity
dotnet run -- rpc timezone # Get server timezone
dotnet run -- rpc sites # Get accessible sites
dotnet run -- rpc custom-fields # Custom fields example
dotnet run -- rpc comprehensive # Test all 18 RPC methods
# Batch
dotnet run -- batch # All batch examples
dotnet run -- batch find # Batch find multiple entity types
dotnet run -- batch add 1 # Batch create assets (transactional, site ID = 1)
dotnet run -- batch mixed # Mixed CRUD + RPC batch
# Diagnostics
dotnet run -- diagnostics # Serialization diagnostics
dotnet run -- help # Show all commandsThe implementation has been verified against the official Fiix API documentation (v2.48.1). The following items are confirmed correct:
| Feature | Status |
|---|---|
FindFilter with Ql query language |
✅ Verified |
Filter.Parameters list |
✅ Verified |
Fields as comma-separated string |
✅ Verified |
OrderBy support |
✅ Verified |
ChangeFields parameter |
✅ Verified |
Singular Object in Add/Change requests |
✅ Verified |
Singular Id in Remove request |
✅ Verified |
Response.Object / Response.Objects |
✅ Verified |
| Hungarian notation field naming | ✅ Verified |
| HMAC SHA256 authentication | ✅ Verified |
ExtraFields for display values |
✅ Verified |
- Batch request support for callback-style patterns (the .NET client uses async/await instead — preferred)
- Synchronous wrapper methods (intentionally omitted; see Design Philosophy)
- Date/time serialization edge cases
- Null value handling in responses
- API-specific error codes
Have a question, found a bug, or want to share feedback? Open a thread in GitHub Discussions.
- fiix-cmms-client-java — official Java client this library is ported from
- fiix-cmms-api-java-examples — official Java examples (patterns apply directly to this library)
Java SDK users: field names, action strings, filter syntax, and request/response shapes are identical. See the Java SDK translation guide in the library README for the small .NET-specific differences.
This project is licensed under the Apache License 2.0. See the LICENSE file for the full text.