SchwabApi.Client 1.0.0-beta.1

This is a prerelease version of SchwabApi.Client.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package SchwabApi.Client --version 1.0.0-beta.1
                    
NuGet\Install-Package SchwabApi.Client -Version 1.0.0-beta.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="SchwabApi.Client" Version="1.0.0-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SchwabApi.Client" Version="1.0.0-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="SchwabApi.Client" />
                    
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 SchwabApi.Client --version 1.0.0-beta.1
                    
#r "nuget: SchwabApi.Client, 1.0.0-beta.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 SchwabApi.Client@1.0.0-beta.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=SchwabApi.Client&version=1.0.0-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=SchwabApi.Client&version=1.0.0-beta.1&prerelease
                    
Install as a Cake Tool

SchwabApi.Client

HTTP client library for making authenticated requests to the Schwab Trading API with automatic retry, circuit breaker, and comprehensive error handling.

Installation

dotnet add package SchwabApi.Client

Or via NuGet Package Manager:

Install-Package SchwabApi.Client

Features

  • HTTP Methods: GET, POST, PUT, PATCH, DELETE with typed request/response models
  • OAuth Integration: Automatic Bearer token injection via IOAuthService
  • Resilience Policies: Retry with exponential backoff, circuit breaker, timeout
  • Error Mapping: HTTP status codes → typed exceptions with context
  • Dependency Injection: ASP.NET Core-ready with IOptions<SchwabApiOptions> configuration
  • Rate Limit Handling: Automatic parsing of X-RateLimit-* headers
  • Thread-Safe: Designed for concurrent request scenarios

Installation

dotnet add package SchwabApi.Client

Quick Start

Important: Follow the Standard Credential Management Pattern to load credentials from environment variables, not hardcoded values. The examples below use placeholder strings for illustration only.

1. Register Services

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

// Load configuration securely
var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false)
    .AddEnvironmentVariables()
    .Build();

var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);

// Get credentials from environment variables (NEVER hardcode)
var appKey = Environment.GetEnvironmentVariable("SCHWAB_APP_KEY") 
    ?? throw new InvalidOperationException("SCHWAB_APP_KEY not set");
var appSecret = Environment.GetEnvironmentVariable("SCHWAB_APP_SECRET")
    ?? throw new InvalidOperationException("SCHWAB_APP_SECRET not set");
var callbackUrl = configuration["Schwab:CallbackUrl"] 
    ?? "https://127.0.0.1:5001/callback";

// Register OAuth service first
services.AddSchwabAuth(options =>
{
    options.ClientId = appKey;
    options.ClientSecret = appSecret;
    options.RedirectUri = callbackUrl;
});

// Register HTTP client
services.AddSchwabApiClient(options =>
{
    options.BaseUrl = "https://api.schwabapi.com";
    options.AppKey = appKey;
    options.AppSecret = appSecret;
    options.RetryCount = 3;
    options.TimeoutSeconds = 30;
});

var serviceProvider = services.BuildServiceProvider();

2. Make Authenticated Requests

using SchwabApi.Client;

var client = serviceProvider.GetRequiredService<ISchwabApiClient>();

// GET request
var accountsResponse = await client.GetAsync<AccountList>("/trader/v1/accounts");
Console.WriteLine($"Status: {accountsResponse.StatusCode}");
Console.WriteLine($"Data: {accountsResponse.Data}");
Console.WriteLine($"Rate Limit Remaining: {accountsResponse.RateLimitRemaining}");

// POST request
var order = new OrderRequest { Symbol = "AAPL", Quantity = 10, Side = "BUY" };
var orderResponse = await client.PostAsync<OrderRequest, OrderResponse>(
    "/trader/v1/accounts/12345/orders",
    order);

// Handle errors
try
{
    var response = await client.GetAsync<Quote>("/marketdata/v1/quotes/INVALID");
}
catch (ResourceNotFoundException ex)
{
    Console.WriteLine($"Not found: {ex.Message}");
    Console.WriteLine($"URL: {ex.RequestUrl}");
}
catch (RateLimitException ex)
{
    Console.WriteLine($"Rate limited. Retry after: {ex.RetryAfter}");
}

3. Configuration via appsettings.json

Note: Store only non-sensitive configuration in appsettings.json. Keep App Key and App Secret in environment variables per the Standard Credential Management Pattern.

{
  "Schwab": {
    "CallbackUrl": "https://127.0.0.1:5001/callback"
  },
  "SchwabApi": {
    "BaseUrl": "https://api.schwabapi.com",
    "TimeoutSeconds": 30,
    "RetryCount": 3,
    "RetryBaseDelaySeconds": 2,
    "CircuitBreakerFailureThreshold": 5,
    "CircuitBreakerCooldownSeconds": 30
  }
}

Then set environment variables for sensitive credentials:

PowerShell:

$env:SCHWAB_APP_KEY = "your_app_key_from_schwab_portal"
$env:SCHWAB_APP_SECRET = "your_app_secret_from_schwab_portal"

Linux/macOS:

export SCHWAB_APP_KEY="your_app_key_from_schwab_portal"
export SCHWAB_APP_SECRET="your_app_secret_from_schwab_portal"
services.AddSchwabApiClient(configuration.GetSection("SchwabApi"));

Configuration Options

Property Type Default Description
BaseUrl string https://api.schwabapi.com Schwab API base URL (must be HTTPS)
AppKey string (required) OAuth client ID
AppSecret string (required) OAuth client secret
TimeoutSeconds int 30 HTTP request timeout (1-300 seconds)
RetryCount int? 3 Number of retry attempts for transient failures (0-10)
RetryBaseDelaySeconds int? 2 Base delay for exponential backoff (1-10 seconds)
CircuitBreakerFailureThreshold int? 5 Failures before circuit opens (1-20)
CircuitBreakerCooldownSeconds int? 30 Cooldown before half-open state (10-300 seconds)

Resilience Policies

Retry Policy

  • Transient Errors: HTTP 500, 502, 503, 504, 408, and timeout exceptions
  • Exponential Backoff: delay = baseDelay^retryAttempt (e.g., 2s, 4s, 8s for 3 retries)
  • No Retry: Client errors (400, 401, 403, 404) are not retried

Circuit Breaker

  • Failure Threshold: Opens circuit after N consecutive failures (default: 5)
  • Cooldown: Remains open for N seconds before testing recovery (default: 30s)
  • Half-Open State: Allows one test request before fully closing

Timeout Policy

  • Per-Request: Applies to each individual HTTP request
  • Pessimistic: Cancels request immediately when timeout expires

Error Handling

The client maps HTTP status codes to typed exceptions:

Status Code Exception Description
400 ValidationException Invalid request parameters
401 AuthenticationException Missing or invalid access token
403 AuthorizationException Insufficient permissions
404 ResourceNotFoundException Endpoint or resource not found
429 RateLimitException Rate limit exceeded, includes RetryAfter
500, 502, 503, 504 ServerException Schwab API server error
Timeout TimeoutException Request exceeded configured timeout

All exceptions include:

  • StatusCode: HTTP status code
  • RequestUrl: Full request URL
  • CorrelationId: Schwab's correlation ID (if available)
  • ResponseBody: Raw response for debugging

Rate Limits

The client automatically parses Schwab's rate limit headers:

var response = await client.GetAsync<Quote>("/marketdata/v1/quotes/AAPL");

Console.WriteLine($"Limit: {response.RateLimitLimit}");
Console.WriteLine($"Remaining: {response.RateLimitRemaining}");
Console.WriteLine($"Reset: {response.RateLimitReset}"); // Unix timestamp

Thread Safety

ISchwabApiClient is registered as a singleton and is thread-safe. It can handle multiple concurrent requests:

var client = serviceProvider.GetRequiredService<ISchwabApiClient>();

var tasks = Enumerable.Range(0, 100).Select(i =>
    client.GetAsync<Quote>($"/marketdata/v1/quotes/SYMBOL{i}"));

var responses = await Task.WhenAll(tasks);

Advanced Scenarios

Custom HttpClient Configuration

services.AddHttpClient("SchwabApiClient", client =>
{
    client.DefaultRequestHeaders.Add("X-Custom-Header", "value");
    client.Timeout = TimeSpan.FromSeconds(60);
});

services.AddSchwabApiClient(options => { /* ... */ });

Logging

The client uses ILogger<SchwabApiClient> for structured logging:

  • Retry Attempts: Logs each retry with delay and reason
  • Circuit Breaker: Logs state changes (OPEN, CLOSED, HALF-OPEN)
  • Timeout Warnings: Logs when requests approach timeout threshold

Enable logging in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "SchwabApi.Client": "Information",
      "SchwabApi.Client.Resilience": "Warning"
    }
  }
}

Correlation IDs

Schwab includes a correlation ID in responses for debugging:

try
{
    var response = await client.GetAsync<Quote>("/marketdata/v1/quotes/AAPL");
}
catch (SchwabApiException ex)
{
    Console.WriteLine($"Correlation ID: {ex.CorrelationId}");
    // Provide this to Schwab support for troubleshooting
}

Testing

Mock ISchwabApiClient in your tests:

var mockClient = new Mock<ISchwabApiClient>();
mockClient
    .Setup(x => x.GetAsync<AccountList>("/trader/v1/accounts", It.IsAny<CancellationToken>()))
    .ReturnsAsync(new ApiResponse<AccountList>
    {
        Data = new AccountList { Accounts = new[] { /* ... */ } },
        StatusCode = HttpStatusCode.OK,
        Headers = new Dictionary<string, string>()
    });

// Inject mock into your service
var yourService = new YourService(mockClient.Object);
  • SchwabApi.Authentication: OAuth 2.0 authorization code flow with PKCE
  • SchwabApi.MarketData: Typed models for market data endpoints
  • SchwabApi.Trading: Typed models for trading endpoints

License

Licensed under the MIT License. See LICENSE in the project root for license information.

Support

Changelog

Version 0.2.0

  • Initial HTTP client implementation
  • OAuth Bearer token injection
  • Polly resilience policies (retry, circuit breaker, timeout)
  • Comprehensive error mapping
  • Rate limit header parsing
  • Dependency injection support
  • Full test coverage (47 tests)
Product 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 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. 
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