SpareApi 0.0.1

dotnet add package SpareApi --version 0.0.1
                    
NuGet\Install-Package SpareApi -Version 0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SpareApi" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SpareApi" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="SpareApi" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SpareApi --version 0.0.1
                    
#r "nuget: SpareApi, 0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SpareApi@0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SpareApi&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=SpareApi&version=0.0.1
                    
Install as a Cake Tool

SpareApi .NET SDK

The official .NET / C# SDK for the SpareApi Open Banking API.

Table of Contents

Installation

dotnet add package SpareApi

Requires .NET 6.0 or later. The package is available on NuGet.

Versioning

The SDK follows Semantic Versioning. Breaking changes are indicated by a major version bump. The changelog is available on the GitHub releases page.

All users are strongly recommended to use a recent version of the library, as older versions may not contain support for new endpoints and fields.

Getting Started

Configuration

using SpareApi;

// 1. Create a Configuration with your credentials
var config = new Configuration(new ConfigurationOptions
{
    AppId  = Environment.GetEnvironmentVariable("SPARE_APP_ID")!,
    ApiKey = Environment.GetEnvironmentVariable("SPARE_API_KEY")!,
    Tenant = Environment.GetEnvironmentVariable("SPARE_TENANT")!,
});

// 2. Pass it to the client — that's it
var client = new SpareApiClient(config);

// Token exchange happens automatically on the first request.
// X-Tenant header is injected on every request.
var providers = await client.Providers.ListAsync(
    new ListProvidersRequest { CountryCode = "AE" }
);
foreach (var provider in providers.Data)
{
    provider.Switch(
        ksa => Console.WriteLine($"{ksa.Id} {ksa.EnglishName}"),
        bahrain => Console.WriteLine($"{bahrain.Id} {bahrain.EnglishName}"),
        uae => Console.WriteLine($"{uae.Id} {uae.Name}")
    );
}

Required environment variables:

Variable Description
SPARE_APP_ID App ID from your Spare dashboard
SPARE_API_KEY API key from your Spare dashboard
SPARE_TENANT Tenant identifier: UAE or KSA

Configuration options

Option Type Default Description
AppId string required App ID from your Spare dashboard
ApiKey string required API key from your Spare dashboard
Tenant string required Tenant identifier: "UAE" or "KSA"
Environment SpareEnvironment SpareEnvironment.Sandbox API environment
BaseUrl string? derived from Environment Override base URL (e.g., local dev server)
RefreshThresholdMs int 1_800_000 (30 min) Time before expiry to proactively refresh
TokenExchangeTimeout TimeSpan 10 seconds Timeout for token exchange/refresh requests

Multi-environment support

// Sandbox (default)
var sandboxConfig = new Configuration(new ConfigurationOptions
{
    AppId = "...", ApiKey = "...", Tenant = "UAE",
    Environment = SpareEnvironment.Sandbox,
});

// Production
var productionConfig = new Configuration(new ConfigurationOptions
{
    AppId = "...", ApiKey = "...", Tenant = "UAE",
    Environment = SpareEnvironment.Production,
});

// Local development
var localConfig = new Configuration(new ConfigurationOptions
{
    AppId = "...", ApiKey = "...", Tenant = "UAE",
    BaseUrl = "http://localhost:4000",   // overrides Environment
});

Error Handling

All non-2xx responses throw a SpareApiApiException. Inspect StatusCode and Body to handle specific error types:

using SpareApi;

try
{
    var consent = await client.PaymentConsents.GetAsync(
    new GetPaymentConsentsRequest { ConsentId = "consent-id" }
);
}
catch (SpareApiApiException e)
{
    Console.Error.WriteLine(e.StatusCode); // HTTP status — e.g. 404
    Console.Error.WriteLine(e.Body);       // raw response body
    throw;
}

Examples

For more examples see the API reference documentation.

List providers

Retrieve the open-banking providers available in a given country:

var providers = await client.Providers.ListAsync(
    new ListProvidersRequest { CountryCode = "AE" }
);

foreach (var provider in providers.Data)
{
    provider.Switch(
        ksa => Console.WriteLine($"{ksa.Id} {ksa.EnglishName}"),
        bahrain => Console.WriteLine($"{bahrain.Id} {bahrain.EnglishName}"),
        uae => Console.WriteLine($"{uae.Id} {uae.Name}")
    );
}

Create a payment request

Payment requests require a request signature. Use client.Crypto.SignPayload to produce the ES256 detached JWS and pass it as Signature:

var body = new Dictionary<string, object>
{
    ["type"]              = "SingleInstantPayment",
    ["purpose"]           = "ACM",
    ["creditorType"]      = "MERCHANT",
    ["creditorReference"] = "INV-10042",
    ["creditorAccount"]   = new Dictionary<string, object>
    {
        ["identification"] = "AE070331234567890123456",
        ["name"]           = "Acme Corp",
        ["schemeName"]     = "IBAN",
    },
    ["instructions"] = new Dictionary<string, object>
    {
        ["amount"] = new Dictionary<string, object>
        {
            ["amount"]   = "250.00",
            ["currency"] = "AED",
        },
    },
};

var xSignature = client.Crypto.SignPayload(
    Environment.GetEnvironmentVariable("SPARE_PRIVATE_KEY_PEM")!, body);

var paymentRequest = await client.PaymentRequests.CreateAsync(
    new CreatePaymentRequestsRequest
    {
        Type = "SingleInstantPayment",
        Purpose = "ACM",
        CreditorType = "MERCHANT",
        CreditorReference = "INV-10042",
        MerchantReference = "INV-10042",
        CreditorAccount = new CreatePaymentRequestsRequestCreditorAccount
        {
            Identification = "AE070331234567890123456",
            Name = "Acme Corp",
            SchemeName = "IBAN",
        },
        Instructions = new PaymentIPaymentInstructionsUae
        {
            Amount = new PaymentIAmountDtoUae { Amount = "250.00", Currency = "AED" },
        },
        Signature = xSignature,
    }
);

Console.WriteLine($"Payment request: {paymentRequest.Data.Id}");
// Redirect the user to this URL to authorise the payment at their bank
Console.WriteLine($"Authorise at: {paymentRequest.Data.RedirectUrl}");

After the user authorises at the bank (via RedirectUrl), consent is created server-side. Poll or sync status using PaymentConsents.ListAsync, PaymentConsents.GetAsync, or PaymentConsents.SyncAsync:

// List all consents (paginated)
var consents = await client.PaymentConsents.ListAsync(
    new ListPaymentConsentsRequest { Page = 1, PerPage = 10 }
);
foreach (var c in consents.Data)
{
    Console.WriteLine($"{c.Id} {c.Status}");
}

// Get a specific consent by ID
var consent = await client.PaymentConsents.GetAsync(
    new GetPaymentConsentsRequest { ConsentId = "consent-id" }
);
Console.WriteLine($"Consent status: {consent.Data.Status}");

// Force a sync from the bank to get the latest status
var synced = await client.PaymentConsents.SyncAsync(
    new SyncPaymentConsentsRequest { PaymentRequestId = paymentRequest.Data.Id }
);
Console.WriteLine($"Synced status: {synced.Data.Status}");

Get a payment

var payment = await client.Payments.GetAsync(
    new GetPaymentsRequest { PaymentId = paymentId }
);

Console.WriteLine($"Status: {payment.Data.Status}");

Register a bank account

var account = await client.BankAccounts.CreateAsync(
    new CreateBankAccountsRequest
    {
        AccountDetails = new CreateBankAccountsRequestAccountDetails
        {
            IdentificationNumber = "AE070331234567890123456",
            Scheme = "IBAN",
        },
        AccountHolderDetails = new CreateBankAccountsRequestAccountHolderDetails
        {
            Name = "Acme Corp",
        },
        Name = "Primary settlement account",
    }
);

Console.WriteLine($"Account registered: {account.Data.Id}");

Schedule a mandate

Scheduling a mandate also requires a request signature:

var mandateBody = new Dictionary<string, object>
{
    ["mandateId"]     = "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    ["amount"]        = 500,
    ["executionDate"] = "2025-06-01",
};

var xSignature = client.Crypto.SignPayload(
    Environment.GetEnvironmentVariable("SPARE_PRIVATE_KEY_PEM")!, mandateBody);

var mandate = await client.Mandates.ScheduleAsync(
    new ScheduleMandatesRequest
    {
        MandateId     = "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        Amount        = 500,
        ExecutionDate = "2025-06-01",
        Signature    = xSignature,
    }
);

Console.WriteLine($"Mandate scheduled: {mandate.Data.Id}");

Verify a beneficiary

Confirm that a beneficiary account (IBAN) matches the expected name before creating a payment, to reduce misdirected-payment risk. No request signature is required.

var verification = await client.Verifications.VerifyBeneficiaryAsync(
    new VerifyBeneficiaryVerificationsRequest
    {
        Iban = "AE070331234567890123456",
        BeneficiaryName = "Acme Corp",
    }
);

Console.WriteLine($"Result: {verification.Data.Result}");
Console.WriteLine($"Matched name: {verification.Data.BeneficiaryName}");

List saved debtor accounts

Debtor (payer) accounts saved against one of your end-customers. The end-customer is identified by the required CustomerId header (a merchant-supplied UUID).

var accounts = await client.SavedDebtorAccounts.ListAsync(
    new ListSavedDebtorAccountsRequest
    {
        CustomerId = Environment.GetEnvironmentVariable("SPARE_CUSTOMER_ID")!,
        Page = 1,
        PerPage = 20,
    }
);

foreach (var account in accounts.Data)
{
    Console.WriteLine($"{account.Id} {account.MaskedIdentification}");
}

Request signing (x-signature)

Certain endpoints — PaymentRequests.CreateAsync and Mandates.ScheduleAsync — require a detached JWS signature, passed via the Signature field. The SDK ships a CryptoHelper accessible via client.Crypto.

Generate a key pair and register the public key in your Spare dashboard:

openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt -out private.pem
openssl ec -in private.pem -pubout -out public.pem   # register public.pem in dashboard

Using CryptoHelper standalone:

using SpareApi;

var crypto = new CryptoHelper();

// Canonical JSON (sorted keys, nulls omitted)
var canonical = crypto.SerializePayload(body);
Console.WriteLine("Canonical: " + canonical);

// ES256 detached JWS — pass as Signature (x-signature header)
var privateKeyPem = Environment.GetEnvironmentVariable("SPARE_PRIVATE_KEY_PEM")!;
var xSignature = crypto.SignPayload(privateKeyPem, body);
Console.WriteLine("x-signature: " + xSignature);  // eyJhbGciOiJFUzI1NiJ9...<sig>

Contributing

See CONTRIBUTING.md.

License

This SDK is distributed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1 0 8/10/2026