Synqony.Api.Sdk
1.5.0
dotnet add package Synqony.Api.Sdk --version 1.5.0
NuGet\Install-Package Synqony.Api.Sdk -Version 1.5.0
<PackageReference Include="Synqony.Api.Sdk" Version="1.5.0" />
<PackageVersion Include="Synqony.Api.Sdk" Version="1.5.0" />
<PackageReference Include="Synqony.Api.Sdk" />
paket add Synqony.Api.Sdk --version 1.5.0
#r "nuget: Synqony.Api.Sdk, 1.5.0"
#:package Synqony.Api.Sdk@1.5.0
#addin nuget:?package=Synqony.Api.Sdk&version=1.5.0
#tool nuget:?package=Synqony.Api.Sdk&version=1.5.0
Synqony.Api.Sdk
.NET SDK that abstracts HTTP communication with a Synqony API backend. Built on top of Synqony.Api.Model, it exposes a strongly-typed SynqonyApiClient that handles authentication, request serialization, and response parsing so consumers can interact with the API through plain C# methods.
Table of Contents
- Installation
- Quick Start
- Architecture
- Configuration
- Authentication
- Resource Clients
- CRUD Operations
- Service Capabilities
- Payment Configuration
- Logging
- Low-Level HTTP Access
Installation
The package is available on nuget.org:
dotnet add package Synqony.Api.Sdk
Supported Frameworks: net10.0, net8.0
Quick Start
using Synqony.Api.Sdk;
using var httpClient = new HttpClient();
var config = new SynqonyApiConfig(
baseUrl: "https://api.synqony.com",
clientId: "<client-id>",
clientSecret: "<client-secret>",
context: "<mandant-or-partnership-id>");
var client = SynqonyApiClient.ForClientCredentials(config, httpClient);
// Load a single address
var address = await client.Addresses.GetById("133716247126412");
// Load a paginated list
var firstPage = await client.Orders.GetList(pageNumber: 0, pageSize: 50);
The client transparently obtains an OAuth access token, attaches it to every request, serializes payloads to JSON, and deserializes responses into the strongly-typed Result<T> / ListInfo<T> wrappers from Synqony.Api.Model.
Architecture
SynqonyApiClient # Top-level facade — entry point
├── Addresses, Contacts # Master data clients
├── Containers, ContainerTypes
├── Services, Vehicles, Drivers, Personnel
├── Orders, Contracts # Transactional data clients
├── Invoices, CashJournals
├── Requisitions, StorageTransactions
└── Users, Permissions, Logs # General clients
│
▼
SynqonyApiHandler # Low-level HTTP send/receive + JSON
│
▼
HttpClient + ITokenProvider # Pluggable transport and auth
SynqonyApiClient– User-facing facade. Bundles all resource clients.- Resource clients (e.g.
AddressesClient) – Implement marker interfaces (IGetById<T>,IGetList<T>,ICreate<T>,IUpdate<T>,IDelete<T>) that unlock matching extension methods. SynqonyApiHandler– WrapsHttpClientand exposes typedGetAsync<T>,PostAsync<T>,PutAsync<T>,PatchAsync<T>,DeleteAsync<T>helpers. Handles bearer-token injection, JSON (de)serialization, and structured logging.ITokenProvider– Abstraction over token retrieval. The defaultOAuthTokenProviderperforms the Client Credentials flow; you can plug in a custom implementation (e.g. for the Password or Refresh Token flow, or to share a token cache across clients).
Configuration
SynqonyApiConfig carries everything the SDK needs to talk to a backend:
| Property | Description |
|---|---|
BaseUrl |
Base URL of the Synqony API server. Trailing / is stripped automatically. |
ClientId |
OAuth client id of the API user. |
ClientSecret |
OAuth client secret of the API user. |
Context |
Authorization context — typically a mandant number or partnership id. |
var config = new SynqonyApiConfig(
baseUrl: "http://localhost:8084",
clientId: "my-client",
clientSecret: "s3cret",
context: "100");
Authentication
The auth mode is always chosen explicitly through a named factory:
// OAuth Client Credentials, console logger
SynqonyApiClient.ForClientCredentials(config, httpClient);
// OAuth Client Credentials, custom logger factory
SynqonyApiClient.ForClientCredentials(config, httpClient, loggerFactory);
// OAuth Password (user credentials) flow with automatic refresh
SynqonyApiClient.ForUser(config, httpClient, username, password, scope, loggerFactory);
// Fully custom token provider (e.g. cached tokens, mocks)
new SynqonyApiClient(config, httpClient, tokenProvider, loggerFactory);
To implement a custom token provider, implement ITokenProvider:
public sealed class MyTokenProvider : ITokenProvider
{
public Task<string> GetToken(CancellationToken cancellationToken = default)
=> Task.FromResult("<access-token>");
}
GetToken is invoked on every request, so caching/refreshing is the provider's responsibility. The built-in OAuthTokenProvider caches the token until it expires.
Resource Clients
Each resource client maps to a top-level API resource and is reachable via a property on SynqonyApiClient.
Some resources expose entity-scoped sub-clients for navigating relationships, accessed through an indexer:
// All contact persons linked to a specific address
var linked = await client.Addresses["133716247126412"].Contacts.GetList();
CRUD Operations
CRUD methods are exposed as extension methods on marker interfaces. A resource client only offers the operations matching the interfaces it implements — calls to unsupported operations won't compile.
using Synqony.Api.Sdk.Clients;
// GET /addresses/{id}
Result<ApiAddress>? single = await client.Addresses.GetById(id);
// GET /addresses/:query?pageNo=0&pageSize=50
Result<ListInfo<ApiAddress>>? page = await client.Addresses.GetList(0, 50);
// POST /addresses/:query (with filters and sort in the body)
var request = new ListRequestInfo
{
Filters =
{
new ColumnFilter
{
ColumnName = nameof(ApiContactPerson.Email),
ColumnValue = "some.name@synqony.com",
Operation = EnumApiFilterOperation.Equal,
},
},
};
Result<ListInfo<ApiContactPerson>>? matches = await client.Contacts.GetList(request);
// POST /addresses
Result<ApiAddress>? created = await client.Addresses.Create(newAddress);
// PUT /addresses/{id}
Result<ApiAddress>? updated = await client.Addresses.Update(modifiedAddress);
// DELETE /addresses/{id}
Result<ApiAddress>? deleted = await client.Addresses.Delete(id);
All responses are wrapped in Result<T> and may contain status Messages in addition to (or instead of) Data. See Synqony.Api.Model for details on common types.
Marker Interfaces
| Interface | Unlocks |
|---|---|
IGetById<T> |
GetById(id, ct) |
IGetListBasic<T> |
GetList(pageNumber, pageSize, ct) (GET, query string) |
IGetListFull<T> |
GetList(ListRequestInfo, ct) (POST, body) |
IGetList<T> |
Both variants above |
ICreate<T> |
Create(payload, ct) |
IUpdate<T> |
Update(payload, ct) (requires T : IId) |
IDelete<T> |
Delete(id, ct) |
IResolve<TQuery, TResult> |
Resolve(query, ct) (POST body, :resolve custom method) |
Service Capabilities
A service capability rule declares whether a service is supported at an address or a storage location — e.g. which materials a disposer accepts, or which waste codes a storage bay may store. The reason a service is or isn't supported is a business decision and out of the SDK's scope; the SDK only lets you maintain the rules and resolve against them.
The two concerns are deliberately separate:
- Maintenance is ordinary CRUD on the rules, scoped to the owning entity. The owner is implied
by the path, so the rule payloads (
ApiAddressServiceCapability,ApiStorageLocationServiceCapability) do not repeat it. - Resolution answers "which entities match these criteria" and is exposed as a
:resolvecustom method (viaIResolve<TQuery, TResult>) that returns full DTOs.
Maintaining rules (CRUD)
Reached through the entity-scoped indexer, mirroring Addresses[id].Contacts. Both owners expose
an identical ServiceCapabilities sub-client with full CRUD:
// GET /addresses/{id}/service_capabilities/:query?pageNo=0&pageSize=50
var rules = await client.Addresses[addressId].ServiceCapabilities.GetList(0, 50);
// POST /addresses/{id}/service_capabilities
await client.Addresses[addressId].ServiceCapabilities.Create(new ApiAddressServiceCapability
{
Service = new LinkableEntity(ResourceType.Services) { ForeignKey = serviceId },
Mode = EnumServiceHandlingMode.DropOff, // customer brings the material to this address
IsAllowed = true,
});
// Same surface for storage locations (uses LegalCode instead of Mode)
await client.StorageLocations[locationId].ServiceCapabilities.Create(new ApiStorageLocationServiceCapability
{
Service = new LinkableEntity(ResourceType.Services) { ForeignKey = serviceId },
LegalCode = "170405",
IsAllowed = true,
});
Resolving
Wildcard rules, priorities and allow/deny are evaluated server-side; the result may contain several equally-suitable matches.
// "Which addresses support this service?"
// POST /addresses/:resolve
Result<ListInfo<ApiAddress>>? addresses = await client.Addresses.Resolve(
new ApiAddressServiceCapabilityQuery
{
Service = serviceId,
Mode = EnumServiceHandlingMode.DropOff,
});
// "Which storage locations accept this service / waste code?"
// POST /storage_locations/:resolve
Result<ListInfo<ApiStorageLocation>>? locations = await client.StorageLocations.Resolve(
new ApiStorageLocationServiceCapabilityQuery { Service = serviceId, LegalCode = "170405" });
Resolution roots on the top-level collection that it returns, which keeps it distinct from the
service_capabilities maintenance path and from plain listing (:query).
Payment Configuration
How payments can be accepted in the current scope. Read-only — a connection is set up by an administrator in the ERP. Each provider is addressed by its own sub-resource, so there is no list and no id:
// GET /payment_configurations/sumup
Result<ApiPaymentConfigurationSumUp>? configuration =
await client.PaymentConfigurations.GetSumUpConfigurationAsync();
The backend answers 404 while SumUp is not set up for the scope — treat that as "no card payment here",
not as an error. Offer card payment only when ActiveState is EnumActiveState.Active, and treat every
other value — including one this package does not know yet — as not usable.
To talk to the provider itself, ask for a short-lived token. The request carries no payload; the credentials it is exchanged for stay on the server:
// POST /payment_configurations/sumup/token:create
Result<ApiPaymentProviderToken>? token =
await client.PaymentConfigurations.CreateSumUpTokenAsync();
Keep the token in memory or the platform keystore, never in logs or crash reports, and do not use it past
ExpiresAt. Every call mints a fresh token, so two calls close together answer with two different tokens,
both valid for their own full lifetime. Ask for one per payment rather than keeping one in reserve: a token
fetched ahead of time may be close to expiry by the time it is used.
Logging
The SDK uses Microsoft.Extensions.Logging and emits structured log entries from SynqonyApiHandler:
| Level | Event |
|---|---|
Debug |
Sending {method} {url} / Received {code} {phrase} |
Trace |
Full request and response bodies |
Warning |
Response body could not be parsed as the expected type |
When no ILoggerFactory is supplied, the SDK falls back to a built-in ConsoleLoggerFactory that writes to standard output. For production, plug in your own factory (e.g. Serilog, NLog, Microsoft.Extensions.Logging.Console):
using var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
var client = SynqonyApiClient.ForClientCredentials(config, httpClient, loggerFactory);
Low-Level HTTP Access
For requests not yet covered by a resource client, you can use SynqonyApiHandler directly. It accepts any Uri, serializes the body, attaches the bearer token, and deserializes the response:
var handler = new SynqonyApiHandler(httpClient, tokenProvider, logger, serializerOptions);
Result<MyDto>? response = await handler.GetAsync<Result<MyDto>>(
new Uri($"{config.BaseUrl}/v1/{config.Context}/custom/endpoint"));
SynqonyApiHandler provides GetAsync<T>, PostAsync<T>, PutAsync<T>, PatchAsync<T>, and DeleteAsync<T>.
See Also
- Synqony.Api.Model – Data contracts, common types, data model overview.
| 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 was computed. 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 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.Logging (>= 10.0.8)
- NodaTime.Serialization.SystemTextJson (>= 1.4.0)
- Synqony.Api.Model (>= 1.5.0)
-
net8.0
- Microsoft.Extensions.Logging (>= 10.0.8)
- NodaTime.Serialization.SystemTextJson (>= 1.4.0)
- Synqony.Api.Model (>= 1.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.