Table of Contents

.NET SDK

XENBIT.ResQueServe.SDK.Client is a typed REST client for the Dashboard and the Realm API. It authenticates as an API client, renews its short-lived access tokens itself, and maps API errors to exceptions. It targets .NET 10 and integrates with Microsoft.Extensions.DependencyInjection.

dotnet add package XENBIT.ResQueServe.SDK.Client

The models and interfaces come from XENBIT.ResQueServe.Core and XENBIT.ResQueServe.Abstractions, see the API reference.

Authentication

Create an API client in your account first, see Authentication. The SDK chooses the method from what you configure:

Configured Behavior
ClientId + ClientSecret Recommended. The SDK requests a token with the client_credentials grant, caches it, and renews it shortly before it expires (TokenRenewalMargin, default 60 seconds). If a request answers 401, it renews the token and repeats the request once.
JwtToken A long-living token, used as it is and never renewed. Legacy: it stays valid until it expires or is revoked. You can replace it at runtime with UpdateToken(...) on a client.

All clients share one token provider and one connection pool, so a token is requested only once. If the token endpoint fails (for example with 429), the SDK does not ask again until the Retry-After time has passed (10 seconds if the server names none) and throws the same OAuthException in the meantime.

Configuration

Option Description
DashboardBaseUrl Base URL of the Dashboard. Required for the Dashboard clients
RealmBaseUrl Base URL of the Realm. Optional for the SIM Dispatcher environments: it is derived from DashboardBaseUrl
AuthorizationServerUrl Base URL of the authorization server (/oauth/token). Optional, the Dashboard of the environment
ClientId, ClientSecret The API client. Keep the secret out of source control
JwtToken Long-living token, used when no ClientId is set
TokenRenewalMargin How long before expiry a token is renewed. Default 60 seconds
TimeoutSeconds Request timeout. Default 30
RequestCulture Sent as Accept-Language. Default is the current UI culture

All URLs must be absolute https URLs. http is accepted only for loopback hosts (localhost, 127.0.0.1), so the client secret and the tokens never travel in clear text.

Note

For the SIM Dispatcher, BaseUrlDefaults contains the hosts: SIMDispatcherDashboard (www.sim-dispatcher.com), SIMDispatcherRealm (platform.sim-dispatcher.com), SIMDispatcherStageDashboard (stage.sim-dispatcher.com) and SIMDispatcherStageRealm (stage-platform.sim-dispatcher.com).

Registration

From configuration (recommended):

{
  "ResQueServe": {
    "DashboardBaseUrl": "https://www.sim-dispatcher.com",
    "ClientId": "YOUR_CLIENT_ID",
    "ClientSecret": "YOUR_CLIENT_SECRET",
    "TimeoutSeconds": 60
  }
}
using XENBIT.ResQueServe.SDK.Extensions;

builder.Services.AddResQueServeClients(builder.Configuration, sectionName: "ResQueServe");

In code:

using XENBIT.ResQueServe.SDK.Defaults;
using XENBIT.ResQueServe.SDK.Extensions;

builder.Services.AddResQueServeClients(options =>
{
    options.DashboardBaseUrl = BaseUrlDefaults.SIMDispatcherDashboard;
    options.ClientId = Environment.GetEnvironmentVariable("RESQUESERVE_CLIENT_ID")!;
    options.ClientSecret = Environment.GetEnvironmentVariable("RESQUESERVE_CLIENT_SECRET")!;
});

AddResQueServeClients registers the Dashboard and the Realm clients. AddResQueServeDashboardClient and AddResQueServeRealmClient register only one of the two.

Clients

The clients are registered as scoped services; inject them where you need them.

Client Purpose
DashboardSystemClient Health and version information
DashboardPoiClient Points of interest
DashboardUnitClient Units (vehicles and responders)
DashboardKeywordClient Keywords (dispatch logic)
DashboardRadioGroupClient Radio groups
DashboardLookupClient Address and geodata lookup
RealmSystemClient System endpoints of the Realm
public class FleetManager(DashboardPoiClient poiClient, DashboardUnitClient unitClient)
{
    public async Task LoadStationsAsync(long dispatchCenterId)
    {
        var stations = await poiClient.GetAllAsync(dispatchCenterId, search: "FindBy('isStation', 'true')");
        // ...
    }
}

Search an address

The dispatch center needs an address package for the lookup.

public async Task<long?> FindAddressIdAsync(DashboardLookupClient client, long dispatchCenterId, string query)
{
    var results = await client.SearchAddressesAsync(dispatchCenterId, query, isInArea: true);
    return results?.FirstOrDefault()?.Id;
}

Create a unit

using XENBIT.ResQueServe.Abstractions.DataStructures;
using XENBIT.ResQueServe.Core.Models;

public async Task CreateAmbulanceAsync(DashboardUnitClient client, long dispatchCenterId, long stationPoiId)
{
    var unit = new Unit
    {
        Callsign = "Rescue 1-83-1",
        CallsignShort = "1-83-1",
        Category = UnitCategory.Ambulance,
        Poi = new Poi { Id = stationPoiId },
        DutyOptions = UnitDutyOptions.Always
    };

    var created = await client.CreateAsync(dispatchCenterId, unit);
}

ShelfOrigin links an entity to an external data source:

unit.Origin = new ShelfOrigin<string>(
    Namespace: "Internal_HR_System",
    Id: "HR_ID_9921",
    Version: new ShelfVersion<string>("v1.0"));

Error handling

API errors are mapped to exceptions derived from ResQueServeException:

try
{
    await unitClient.CreateAsync(dispatchCenterId, unit);
}
catch (ValidationException ex) when (ex.Problems.Contains(ProblemInformation.CallsignAlreadyExists))
{
    // a specific problem, identified by the ProblemInformation enum
}
catch (DataNotFoundException ex)
{
    Console.WriteLine($"Not found: {ex.LookupProperty} = {ex.ProvidedValue}");
}
catch (OAuthException ex) when (ex.RequiresNewCredentials)
{
    // invalid_client / unauthorized_client: the API client needs new credentials, a retry will not help
}
catch (ResQueServeException ex)
{
    // any other platform error; log the trace identifier for support
    logger.LogError(ex, "API error");
}

OAuthException is thrown when the token endpoint rejects a request. Error holds the RFC 6749 error code (for example invalid_client or too_many_requests), StatusCode the HTTP status. Errors without a mapped type are an UnknownException with StatusCode, ResponseContent and ProblemDetails.