MvService.Cbm.ExternalClient
1.0.0
dotnet add package MvService.Cbm.ExternalClient --version 1.0.0
NuGet\Install-Package MvService.Cbm.ExternalClient -Version 1.0.0
<PackageReference Include="MvService.Cbm.ExternalClient" Version="1.0.0" />
<PackageVersion Include="MvService.Cbm.ExternalClient" Version="1.0.0" />
<PackageReference Include="MvService.Cbm.ExternalClient" />
paket add MvService.Cbm.ExternalClient --version 1.0.0
#r "nuget: MvService.Cbm.ExternalClient, 1.0.0"
#:package MvService.Cbm.ExternalClient@1.0.0
#addin nuget:?package=MvService.Cbm.ExternalClient&version=1.0.0
#tool nuget:?package=MvService.Cbm.ExternalClient&version=1.0.0
MvService.Cbm.ExternalClient
.NET client library for the Contract Billing Management (CBM) External API v1.
Covers:
- Contracts (read + line-item write)
- On-demand billing items (generic + typed time/material/usage ingress)
- Asset bulk-import
- ERP order event ingestion
- Customer management (create, read, update, lookup by external ref)
- Subscription lifecycle (trial, plan change, cancellation, status)
Installation
<PackageReference Include="MvService.Cbm.ExternalClient" Version="1.*" />
Add the GitHub Packages source to your nuget.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="github-mv-service"
value="https://nuget.pkg.github.com/MV-Service/index.json" />
</packageSources>
<packageSourceCredentials>
<github-mv-service>
<add key="Username" value="%GITHUB_USERNAME%" />
<add key="ClearTextPassword" value="%GITHUB_TOKEN%" />
</github-mv-service>
</packageSourceCredentials>
</configuration>
GITHUB_TOKEN needs read:packages scope (classic PAT or fine-grained with read access to the MV-Service org packages).
Quickstart
X-Api-Key authentication (default)
builder.Services
.AddCbmExternalClient(o =>
{
o.BaseUrl = "https://cbm.example.com";
o.ApiKey = "your-api-key";
})
.AddStandardResilienceHandler(); // optional — requires Microsoft.Extensions.Http.Resilience
OAuth 2.0 client_credentials flow
builder.Services
.AddCbmExternalClient(o =>
{
o.BaseUrl = "https://cbm.example.com";
o.UseOAuthFlow = true;
o.ClientId = "your-marketplace-app-id";
o.ClientSecret = "your-client-secret";
o.Scope = "contracts:read billing:write assets:write";
});
Tokens are cached and refreshed automatically (30-second buffer before expiry).
Basic usage
public class MyService(ICbmExternalClient cbm)
{
public async Task RunAsync(CancellationToken ct)
{
// List active contracts
var page = await cbm.ListContractsAsync(
status: CbmContractStatus.Active, ct: ct);
foreach (var contract in page.Items)
Console.WriteLine($"{contract.ContractNumber} — {contract.CustomerName}");
// Submit a time entry
var entry = await cbm.SubmitTimeEntryAsync(new CbmTimeEntryRequest
{
CustomerExternalRef = "CUST-001",
Hours = 2.5m,
OccurredAt = DateTime.UtcNow,
Description = "Maintenance work",
}, ct);
Console.WriteLine($"Recorded: {entry.Id}");
// Bulk-import assets
var result = await cbm.ImportAssetsAsync(new CbmBulkImportRequest
{
ThirdPartyAppId = Guid.Parse("..."),
Assets =
[
new CbmBulkImportAssetItem
{
ExternalAssetId = "SRV-001",
Name = "Web Server 01",
AssetType = "Server",
ExternalReferenceUrl = "https://idoit.example.com/cmdb/1234",
},
],
}, ct);
Console.WriteLine($"Imported: {result.Created} created, {result.Updated} updated");
}
}
API Reference
| Method | Description |
|---|---|
GetTokenAsync |
Fetch OAuth token (client_credentials) |
ListContractsAsync |
Paged contract list with optional status/customer filter |
GetContractAsync |
Single contract by ID |
GetLineItemsAsync |
All line items for a contract |
CreateLineItemAsync |
Add a line item to a contract |
GetOnDemandItemsAsync |
On-demand items for a billing run |
AddOnDemandItemAsync |
Add a generic on-demand item to a billing run |
SubmitTimeEntryAsync |
Typed time-entry ingress (auto-matched to contract) |
SubmitMaterialEntryAsync |
Typed material-entry ingress |
SubmitUsageEntryAsync |
Typed usage/consumption entry ingress |
SendErpOrderEventAsync |
Notify CBM of a new ERP order (may auto-create contract) |
ImportAssetsAsync |
Bulk-import assets from a third-party system |
RegisterWebhookAsync |
Register a webhook subscription |
ListWebhooksAsync |
List all webhook subscriptions |
UpdateWebhookAsync |
Update an existing webhook subscription |
DeleteWebhookAsync |
Delete a webhook subscription |
| Customers | |
CreateCustomerAsync |
Create a new customer |
GetCustomerAsync |
Get a customer by ID (returns null if not found) |
FindCustomerByExternalRefAsync |
Find a customer by external reference (returns null if not found) |
UpdateCustomerAsync |
Update an existing customer |
| Subscriptions | |
StartTrialAsync |
Start a trial subscription for a customer |
ChangePlanAsync |
Upgrade or downgrade a customer's subscription plan |
CancelSubscriptionAsync |
Cancel a customer's subscription (optionally at period end) |
GetSubscriptionAsync |
Get a customer's current subscription status (returns null if none) |
Webhook Event Types
Use the CbmWebhookEventTypes constants class when registering webhook subscriptions:
await cbm.RegisterWebhookAsync(new CbmCreateWebhookRequest
{
Url = "https://my-app.example.com/webhooks/cbm",
EventTypes =
[
CbmWebhookEventTypes.CustomerCreated,
CbmWebhookEventTypes.SubscriptionTrialStarted,
CbmWebhookEventTypes.SubscriptionUpgraded,
CbmWebhookEventTypes.SubscriptionCancelled,
],
});
Available event types:
| Constant | Value |
|---|---|
CustomerCreated |
customer.created |
CustomerUpdated |
customer.updated |
ContractCreated |
contract.created |
ContractUpdated |
contract.updated |
ContractCancelled |
contract.cancelled |
ContractAccepted |
contract.accepted |
BillingRunCompleted |
billing_run.completed |
BillingRunItemAdded |
billingrun.item_added |
BillingRunAboutToClose |
billing_run.about_to_close |
SubscriptionTrialStarted |
subscription.trial_started |
SubscriptionUpgraded |
subscription.upgraded |
SubscriptionDowngraded |
subscription.downgraded |
SubscriptionCancelled |
subscription.cancelled |
SubscriptionTrialExpiring |
subscription.trial_expiring |
SubscriptionTrialExpired |
subscription.trial_expired |
Error handling
All HTTP errors surface as typed exceptions:
| Exception | HTTP status |
|---|---|
CbmUnauthorizedException |
401 |
CbmForbiddenException |
403 |
CbmNotFoundException |
404 |
CbmRateLimitException |
429 — includes RetryAfter timespan |
CbmClientException |
Any other non-2xx |
All exceptions expose StatusCode and ResponseBody for diagnostics.
Resilience
AddCbmExternalClient returns an IHttpClientBuilder. Chain
.AddStandardResilienceHandler() (from Microsoft.Extensions.Http.Resilience) or any
Polly pipeline you need:
builder.Services
.AddCbmExternalClient(o => { ... })
.AddResilienceHandler("cbm", pipeline =>
{
pipeline.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3 });
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions());
});
License
UNLICENSED — internal MV-Service component.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.