.NET client for the QubitOn API. Validate addresses, tax IDs, bank accounts, phones, and emails; look up business registrations and corporate hierarchies; screen for sanctions, PEPs, disqualified directors, EPA prosecutions, and healthcare exclusions; assess credit, ESG, and entity risk.
Targets: netstandard2.0, net6.0, net8.0, net10.0.
dotnet add package Qubiton.SdkOr via PackageReference:
<PackageReference Include="Qubiton.Sdk" Version="0.2.0" />The SDK is designed to be long-lived — allocate one QubitOnClient per process and reuse it
(it owns connection pools and an OAuth token cache). The throwaway using shown here is fine
for a one-shot program (CLI, script, console demo); for any service-like workload prefer the
Dependency Injection section below or the
Bring Your Own HttpClient pattern.
using Qubiton.Sdk;
using Qubiton.Sdk.Models;
// One-shot script — `using` is fine because the process exits right after.
using var client = new QubitOnClient(new QubitOnClientOptions { ApiKey = "svm..." });
var resp = await client.ValidateAddressAsync(new AddressRequest
{
AddressLine1 = "123 Main St",
City = "New York",
State = "NY",
PostalCode = "10001",
Country = "US",
});
Console.WriteLine($"Standardized: {resp.Address1}, {resp.City} {resp.State} {resp.PostalCode}");The SDK supports two authentication modes — configure exactly one.
The key is sent as the lowercase apikey header (the .NET API server expects this name; X-Api-Key is ignored).
var client = new QubitOnClient(new QubitOnClientOptions { ApiKey = "svm..." });The SDK exchanges your {ClientId, ClientSecret} pair against POST /api/oauth/token for a short-lived bearer token, caches it, and refreshes automatically. On a 401 response the cached token is invalidated and the request is retried once with a fresh token.
var client = new QubitOnClient(new QubitOnClientOptions
{
ClientId = "your-key",
ClientSecret = "your-secret",
// Optional: TokenUrl = "https://api.qubiton.com/api/oauth/token",
});using Qubiton.Sdk;
services.AddQubiton(o =>
{
o.ApiKey = builder.Configuration["Qubiton:ApiKey"];
o.Timeout = TimeSpan.FromSeconds(30);
});
// Resolve in your code:
public class MyService(IQubitOnClient qubiton) { ... }AddQubiton registers the client as a singleton backed by IHttpClientFactory, so the underlying socket pool is owned by the host. The SDK does not mutate the factory-managed HttpClient's Timeout — per-request timeouts are enforced internally via a linked CancellationTokenSource.
Reuse a long-lived HttpClient (or one provided by IHttpClientFactory). Do not allocate a new HttpClient per call — that pattern leaks sockets and is the most common source of SocketException in production.
// Recommended: get an HttpClient from IHttpClientFactory.
var http = httpClientFactory.CreateClient("qubiton");
using var client = new QubitOnClient(new QubitOnClientOptions { ApiKey = "svm..." }, http);
// Acceptable: long-lived static HttpClient (one per process).
private static readonly HttpClient SharedHttp = new HttpClient();
using var client = new QubitOnClient(new QubitOnClientOptions { ApiKey = "svm..." }, SharedHttp);When you supply an HttpClient the SDK uses it as-is and does not mutate its BaseAddress, DefaultRequestHeaders, or Timeout. The per-request timeout is enforced inside the SDK using a linked CancellationTokenSource so caller-supplied clients are never reconfigured.
| Category | Method | Endpoint |
|---|---|---|
| Address | ValidateAddressAsync |
POST /api/address/validate |
| Tax | ValidateTaxAsync |
POST /api/tax/validate |
| Tax | ValidateTaxFormatAsync |
POST /api/tax/format-validate |
| Tax | GetSupportedTaxFormatsAsync |
GET /api/tax/format-validate/countries |
| Bank | ValidateBankAccountAsync |
POST /api/bank/validate |
| Bank | ValidateBankProAsync |
POST /api/bankaccount/pro/validate |
ValidateEmailAsync |
POST /api/email/validate |
|
| Phone | ValidatePhoneAsync |
POST /api/phone/validate |
| Business Registration | LookupBusinessRegistrationAsync |
POST /api/businessregistration/lookup |
| Peppol | ValidatePeppolAsync |
POST /api/peppol/validate |
| Peppol | GetPeppolSchemesAsync |
GET /api/peppol/schemes |
| Sanctions | CheckSanctionsAsync |
POST /api/prohibited/lookup |
| PEP | ScreenPepAsync |
POST /api/pep/lookup |
| Directors | CheckDirectorsAsync |
POST /api/disqualifieddirectors/validate |
| EPA | CheckEpaProsecutionAsync |
POST /api/criminalprosecution/validate |
| EPA | LookupEpaProsecutionAsync |
POST /api/criminalprosecution/lookup |
| Healthcare | CheckHealthcareExclusionAsync |
POST /api/providerexclusion/validate |
| Healthcare | LookupHealthcareExclusionAsync |
POST /api/providerexclusion/lookup |
| Healthcare | LookupHealthcareAsync |
POST /api/healthcare/lookup |
| Risk | CheckBankruptcyRiskAsync |
POST /api/risk/riskcontrol (Bankruptcy) |
| Risk | LookupCreditScoreAsync |
POST /api/risk/riskcontrol (Credit Score) |
| Risk | LookupFailRateAsync |
POST /api/risk/riskcontrol (Fail Rate) |
| Risk | AssessEntityRiskAsync |
POST /api/entity/fraud/lookup |
| Risk | LookupCreditAnalysisAsync |
POST /api/creditanalysis/lookup |
| ESG | LookupEsgScoreAsync |
POST /api/esg/Scores |
| Cybersecurity | DomainSecurityReportAsync |
POST /api/itsecurity/domainreport |
| Cybersecurity | CheckIpQualityAsync |
POST /api/ipquality/validate |
| Ownership | LookupBeneficialOwnershipAsync |
POST /api/beneficialownership/lookup |
| Hierarchy | LookupCorporateHierarchyAsync |
POST /api/corporatehierarchy/lookup |
| Hierarchy | LookupHierarchyAsync |
POST /api/company/hierarchy/lookup |
| DUNS | LookupDunsAsync |
POST /api/duns-number-lookup |
| Healthcare | ValidateNpiAsync |
POST /api/nationalprovideridentifier/validate |
| Healthcare | ValidateMedpassAsync |
POST /api/medpass/validate |
| DOT | LookupDotCarrierAsync |
POST /api/dot/fmcsa/lookup |
| Identity | ValidateIndiaIdentityAsync |
POST /api/inidentity/validate |
| Certification | ValidateCertificationAsync |
POST /api/certification/validate |
| Certification | LookupCertificationAsync |
POST /api/certification/lookup |
| Classification | LookupBusinessClassificationAsync |
POST /api/businessclassification/lookup |
| Classification | GetNaicsCodesAsync |
GET /api/businessclassification/naics/{digits} |
| Classification | GetSicCodesAsync |
GET /api/businessclassification/sic/{digits} |
| Payment Terms | AnalyzePaymentTermsAsync |
POST /api/paymentterms/validate |
| SAP Ariba | LookupAribaSupplierAsync |
POST /api/aribasupplierprofile/lookup |
| SAP Ariba | ValidateAribaSupplierAsync |
POST /api/aribasupplierprofile/validate |
| Gender | IdentifyGenderAsync |
POST /api/genderize/identifygender |
| Bulk | CheckBulkStatusAsync |
POST /api/bulkstatus/check |
| Method | Reason |
|---|---|
LookupExchangeRatesAsync |
Endpoint marked internal (ApiExplorerSettings(IgnoreApi=true)) — may change without notice. |
ScreenContinuousAsync |
Endpoint is currently a stub returning HTTP 501. |
var tax = await client.ValidateTaxAsync(new TaxRequest
{
IdentityNumber = "12-3456789",
IdentityNumberType = "EIN",
Country = "US",
EntityName = "Acme Inc.",
});
Console.WriteLine($"Identity: {tax.IdentityNumber}, Authority record: {tax.TaxAdditionalInfo?.EntityNameFromTaxAuthorityRecord}");var matches = await client.CheckSanctionsAsync(new SanctionsRequest
{
CompanyName = "Acme Trading Ltd",
Country = "US",
});
foreach (var m in matches)
{
if (m.IsMatch)
Console.WriteLine($"MATCH (score: {m.Score}) — {m.Description}");
}using System;
using System.Collections.Generic;
var pep = await client.ScreenPepAsync(new PepRequest { Name = "John Doe", Country = "US" });
foreach (var entry in pep)
{
foreach (var person in entry.Persons ?? Array.Empty<PepPerson>())
{
Console.WriteLine($"{person.Name} (score: {entry.Score})");
}
// Embedded RCAs (premium PEP catalog clients only) — relatives & close associates.
foreach (var rca in entry.RelativesAndAssociates ?? Array.Empty<RcaPersonResponse>())
{
Console.WriteLine($" RCA: {rca.Name} ({rca.RelationshipType}, {rca.RelationshipStatus})");
}
}var dq = await client.CheckDirectorsAsync(new DirectorsRequest
{
FirstName = "John",
LastName = "Doe",
Country = "GB",
});The endpoint returns a list of registration records — iterate the response directly.
var br = await client.LookupBusinessRegistrationAsync(new BusinessRegistrationRequest
{
EntityName = "Apple Inc.",
Country = "US",
State = "CA",
});
foreach (var reg in br)
{
foreach (var match in reg.BusinessRegistrations ?? Array.Empty<BusinessRegistration>())
{
Console.WriteLine($"{match.RegistrationId}: {match.EntityName} ({match.Status})");
}
}var fmt = await client.ValidateTaxFormatAsync(new TaxFormatRequest
{
CountryIso2 = "US",
IdentityNumberType = "EIN",
IdentityNumber = "12-3456789",
});These endpoints return a single RiskControlResponse (not a list).
var bk = await client.CheckBankruptcyRiskAsync(new BankruptcyRequest
{
CompanyName = "Acme Corp",
Country = "US",
});
Console.WriteLine($"{bk.Category} → {bk.Recommendation} (cases: {bk.Cases?.Count ?? 0})");LookupHealthcareAsync requires an 18-character NetworkEntityId.
var hc = await client.LookupHealthcareAsync(new HealthcareLookupRequest
{
HealthCareType = "HCO",
NetworkEntityId = "AAAAAAAAAAAAAAAAAA",
});var pro = await client.ValidateBankProAsync(new BankProRequest
{
AccountNumber = "1234567890",
BankNumberType = "ACCOUNT",
Country = "US",
BankCode = "021000021",
SwiftCode = "CHASUS33",
});
Console.WriteLine($"Match score: {pro.Score}, code: {pro.MatchCode}");
foreach (var flag in pro.BankProValidations?.RedFlagReasons ?? Array.Empty<string>())
Console.WriteLine($" red flag: {flag}");BankNumberType is the discriminator the server uses to route the request — set it to
IBAN, ACCOUNT, CLABE, CBU, etc. Without it the server falls back to inference
which can produce a 400 for ambiguous inputs.
var bank = await client.ValidateBankAccountAsync(new BankRequest
{
BusinessEntityType = "Corporation",
Country = "AR",
BankAccountHolder = "Acme SA",
BankNumberType = "CBU",
CBU = "0170001540000001234567",
});var status = await client.CheckBulkStatusAsync(new BulkStatusRequest
{
CallBackId = Guid.Parse("6611e45f-f625-421b-aa72-a11925920aef"),
});
Console.WriteLine($"{status.Status}: {status.ProcessedRecords}/{status.TotalRecords}");var h = await client.LookupHierarchyAsync(new HierarchyRequest
{
Identifier = "123456789",
IdentifierType = "DUNS",
Country = "US",
});
void Print(HierarchyCompany? c, int depth = 0)
{
if (c is null) return;
Console.WriteLine(new string(' ', depth * 2) + c.CompanyName);
foreach (var child in c.Children ?? Array.Empty<HierarchyCompany>()) Print(child, depth + 1);
}
Print(h.Parent);var ch = await client.LookupCorporateHierarchyAsync(new CorporateHierarchyRequest
{
CompanyName = "Apple Inc.",
AddressLine1 = "1 Apple Park Way",
City = "Cupertino",
State = "CA",
ZipCode = "95014",
});
foreach (var entry in ch)
{
Console.WriteLine($"Transaction: {entry.ELSGenericMessage?.TransactionId}");
}var duns = await client.LookupDunsAsync(new DunsRequest { DunsNumber = "123456789" });
foreach (var d in duns)
{
// Per-supplier detail items are returned in provider-shape (opaque). Drill in
// via the JsonElement dictionary.
foreach (var item in d.DnbDirectSupplierDetailItems ?? Array.Empty<IReadOnlyDictionary<string, System.Text.Json.JsonElement>>())
{
if (item.TryGetValue("companyName", out var cn))
Console.WriteLine($"DUNS hit → {cn}");
}
}var mp = await client.ValidateMedpassAsync(new MedpassRequest
{
ID = "1234567890",
BusinessEntityType = "Corporation",
CompanyName = "Acme Health Inc.",
});
Console.WriteLine($"Status: {mp.Status} (last updated {mp.LastUpdatedDate:O})");All errors thrown by the SDK derive from QubitonException.
try
{
var resp = await client.ValidateAddressAsync(req);
}
catch (QubitonRateLimitException ex)
{
// 429 — back off using ex.RetryAfter
}
catch (QubitonAuthException ex)
{
// 401 / 403
}
catch (QubitonTimeoutException ex)
{
// Per-request timeout fired (Options.Timeout). The inner exception is the
// underlying OperationCanceledException for diagnostics. Distinct from
// OperationCanceledException raised by caller-supplied CancellationToken cancellation.
}
catch (QubitonValidationException ex)
{
// 4xx (other than auth/404/429)
}
catch (QubitonNotFoundException)
{
// 404
}
catch (QubitonServerException ex)
{
// 5xx or transport
}Note: QubitonTimeoutException derives from QubitonException (status code 0) — placing
its catch above QubitonServerException ensures it's matched first; otherwise the more general
server catch would fire on transport-level failures.
The SDK retries 408, 429 and 5xx automatically (up to MaxRetries, default 3) with exponential backoff (base * 2^attempt) plus 0–25% jitter ABOVE the floor (so the actual delay is always at least the base / Retry-After value), capped at 30 seconds. The Retry-After header is honored as a floor.
A 401 from the server when OAuth is configured triggers a single transparent token refresh + retry — that retry does not consume one of your MaxRetries attempts.
var client = new QubitOnClient(new QubitOnClientOptions
{
ApiKey = "svm...",
BaseUrl = "https://api.qubiton.com", // default
Timeout = TimeSpan.FromSeconds(30), // default
MaxRetries = 3, // default; clamped to >=1
MaxBackoff = TimeSpan.FromSeconds(30), // default; clamped to <=30s
RequestedByClient = "my-app/1.2.3", // optional override; defaults to SDK UA
OAuth2TokenSkew = TimeSpan.FromSeconds(30), // default; clamped to [0s, 5min]
MaxConnectionsPerServer = 100, // default; net6+ only
IdempotencyKey = null, // optional X-Idempotency-Key header
});MaxRetries is the total attempt count (initial + retries). A value of 1 disables retries. MaxBackoff is clamped at 30 seconds; values above 30 seconds are silently reduced. Setting RequestedByClient overrides the value injected into request bodies for the server-required RequestedByClient field; if unset, the SDK uses its own user-agent string. RequestedByClient is truncated at 350 characters (server [MaxLength(350)]). The SDK does not retry HTTP 501 responses (the upstream is explicitly telling us the endpoint isn't implemented).
This API is also available as a native Model Context Protocol (MCP) server.
- Sign up at www.qubiton.com.
- Navigate to Dashboard → API Keys.
- Copy your API key (starts with
svm).
MIT