A comprehensive, production-ready .NET 8 client library for RevenueCat REST API v2. Built with Refit for type-safe HTTP calls. Complete implementation with 50+ models, 20+ enums, full CRUD operations, pagination, expandable fields, search, virtual currency, subscription management, and advanced error handling. Supports all platforms: iOS, Android, Web, Stripe, Amazon, Roku, and Paddle.
$ dotnet add package RevenueCat.NET, # RevenueCat.NET
A professional, production-ready .NET 8 client library for the RevenueCat REST API v2. Built with Refit for type-safe, reliable HTTP communication.
dotnet add package RevenueCat.NET
using RevenueCat.NET;
var client = new RevenueCatClient("your_v2_api_key");
var projects = await client.Projects.ListAsync();
var customers = await client.Customers.ListAsync("proj_abc123");
var customer = await client.Customers.GetAsync(
"proj_abc123",
"customer_id",
expand: new[] { "attributes" }
);
var client = new RevenueCatClient("your_api_key", options =>
{
options.Timeout = TimeSpan.FromSeconds(60);
options.MaxRetryAttempts = 5;
options.RetryDelay = TimeSpan.FromSeconds(1);
options.EnableRetryOnRateLimit = true;
});
var projects = await client.Projects.ListAsync(limit: 20);
var apps = await client.Apps.ListAsync("proj_abc123");
var app = await client.Apps.CreateAsync("proj_abc123", new CreateAppRequest(
Name: "My iOS App",
Type: AppType.AppStore,
AppStore: new AppStoreConfig(
BundleId: "com.example.app",
SharedSecret: "your_shared_secret"
)
));
// Create a customer with attributes
var customer = await client.Customers.CreateAsync("proj_abc123", new CreateCustomerRequest(
Id: "user_12345",
Attributes: new[]
{
new CustomerAttributeInput("$email", "user@example.com"),
new CustomerAttributeInput("$displayName", "John Doe")
}
));
// Grant an entitlement to a customer
await client.Customers.GrantEntitlementAsync("proj_abc123", "customer_id",
new GrantEntitlementRequest(
EntitlementId: "ent_premium",
ExpiresAt: DateTimeOffset.UtcNow.AddMonths(1).ToUnixTimeMilliseconds()
));
// Revoke a granted entitlement
await client.Customers.RevokeGrantedEntitlementAsync("proj_abc123", "customer_id",
new RevokeGrantedEntitlementRequest(EntitlementId: "ent_premium"));
// Assign an offering override
await client.Customers.AssignOfferingAsync("proj_abc123", "customer_id",
new AssignOfferingRequest(OfferingId: "offering_special"));
// Transfer customer data
await client.Customers.TransferAsync("proj_abc123", "old_customer_id",
new TransferCustomerRequest("new_customer_id"));
var products = await client.Products.ListAsync(
"proj_abc123",
appId: "app_xyz789",
expand: new[] { "items.app" }
);
var product = await client.Products.CreateAsync("proj_abc123", new CreateProductRequest(
StoreIdentifier: "com.example.premium.monthly",
AppId: "app_xyz789",
Type: ProductType.Subscription,
DisplayName: "Premium Monthly"
));
var entitlement = await client.Entitlements.CreateAsync("proj_abc123",
new CreateEntitlementRequest(
LookupKey: "premium",
DisplayName: "Premium Access"
));
await client.Entitlements.AttachProductsAsync("proj_abc123", "ent_abc123",
new AttachProductsRequest(new[] { "prod_123", "prod_456" }));
var offering = await client.Offerings.CreateAsync("proj_abc123",
new CreateOfferingRequest(
LookupKey: "default",
DisplayName: "Default Offering",
IsDefault: true
));
var package = await client.Packages.CreateAsync("proj_abc123", "offering_id",
new CreatePackageRequest(
LookupKey: "monthly",
DisplayName: "Monthly Package",
ProductId: "prod_123",
Position: 1
));
var subscriptions = await client.Subscriptions.ListAsync("proj_abc123", "customer_id");
await client.Subscriptions.CancelAsync("proj_abc123", "customer_id", "sub_id");
await client.Subscriptions.RefundAsync("proj_abc123", "customer_id", "sub_id");
var purchases = await client.Purchases.ListAsync("proj_abc123", "customer_id");
await client.Purchases.RefundAsync("proj_abc123", "customer_id", "purchase_id");
var metrics = await client.Charts.GetMetricsAsync(
"proj_abc123",
ChartMetricType.Revenue,
startDate: 1704067200000, // Unix timestamp
endDate: 1735689600000,
appId: "app_123"
);
try
{
var customer = await client.Customers.GetAsync("proj_abc123", "customer_id");
}
catch (NotFoundException ex)
{
Console.WriteLine($"Customer not found: {ex.Message}");
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited. Retry after: {ex.ErrorResponse?.BackoffMs}ms");
}
catch (RevenueCatException ex)
{
Console.WriteLine($"API error: {ex.Message}");
Console.WriteLine($"Error type: {ex.ErrorResponse?.Type}");
}
services.AddSingleton<IRevenueCatClient>(sp =>
new RevenueCatClient("your_api_key"));
Comprehensive examples are available in the examples/ directory:
See the examples README for detailed information.
Reduce API calls by expanding related resources:
var customer = await client.Customers.GetAsync(
projectId,
customerId,
expand: new[] { "attributes", "active_entitlements" }
);
// Access expanded data directly
foreach (var attr in customer.Attributes.Items)
{
Console.WriteLine($"{attr.Key}: {attr.Value}");
}
Efficiently handle large datasets:
string? startingAfter = null;
do
{
var page = await client.Customers.ListAsync(
projectId,
limit: 100,
startingAfter: startingAfter
);
// Process page.Items
// Get cursor for next page
if (page.NextPage != null)
{
var uri = new Uri(page.NextPage);
var query = HttpUtility.ParseQueryString(uri.Query);
startingAfter = query["starting_after"];
}
else
{
startingAfter = null;
}
} while (startingAfter != null);
Search by store identifiers:
// Search subscriptions
var subscriptions = await client.Subscriptions.SearchSubscriptionsAsync(
projectId,
storeSubscriptionIdentifier: "GPA.1234-5678-9012-34567"
);
// Search purchases
var purchases = await client.Purchases.SearchPurchasesAsync(
projectId,
storePurchaseIdentifier: "1000000123456789"
);
Manage in-app currencies:
// Add currency (multiple currencies at once)
var balances = await client.Customers.CreateVirtualCurrencyTransactionAsync(
projectId,
customerId,
new CreateVirtualCurrencyTransactionRequest(
Adjustments: new Dictionary<string, int>
{
{ "GEMS", 100 },
{ "COINS", 500 }
},
Reference: "purchase_reward"
),
idempotencyKey: "unique-key"
);
// Update balance directly (without transaction record)
await client.Customers.UpdateVirtualCurrencyBalanceAsync(
projectId,
customerId,
new UpdateVirtualCurrencyBalanceRequest(
Adjustments: new Dictionary<string, int>
{
{ "GEMS", 50 } // Set absolute balance
}
),
idempotencyKey: "another-unique-key"
);
// List all balances
var allBalances = await client.Customers.ListVirtualCurrencyBalancesAsync(
projectId,
customerId,
includeEmptyBalances: true
);
Transfer data between customers:
var transfer = await client.Customers.TransferAsync(
projectId,
sourceCustomerId,
new TransferCustomerRequest(
TargetCustomerId: targetCustomerId,
AppIds: new[] { "app_123" } // Optional: filter by apps
)
);
| Resource | List | Get | Create | Update | Delete | Actions |
|---|---|---|---|---|---|---|
| Projects | ✅ | - | - | - | - | - |
| Apps | ✅ | ✅ | ✅ | ✅ | ✅ | Get StoreKit Config, List API Keys |
| Customers | ✅ | ✅ | ✅ | - | ✅ | Transfer, Grant/Revoke Entitlement, Assign Offering, Manage Attributes |
| Products | ✅ | ✅ | ✅ | - | ✅ | Create in Store |
| Entitlements | ✅ | ✅ | ✅ | ✅ | ✅ | Attach/Detach Products |
| Offerings | ✅ | ✅ | ✅ | ✅ | ✅ | Set Default |
| Packages | ✅ | ✅ | ✅ | ✅ | ✅ | Attach/Detach Products |
| Paywalls | - | - | ✅ | - | - | - |
| Subscriptions | ✅ | ✅ | - | - | - | Cancel, Refund, Get Management URL, List Transactions |
| Purchases | ✅ | ✅ | - | - | - | Refund |
| Invoices | ✅ | - | - | - | - | Get File URL |
| Charts | - | ✅ | - | - | - | Get Overview Metrics |
| Virtual Currency | ✅ | - | ✅ | ✅ | - | - |
See MIGRATION.md for detailed migration instructions from version 1.x to 2.0.
MIT License - see LICENSE file for details
Contributions are welcome! Please feel free to submit a Pull Request.
frknlkn - GitHub
For issues and questions: