BIN Lookup API for C#: Integration Guide
This guide walks you through integrating BINLookupAPI into your C#/.NET application, from creating your account to writing production-ready code with proper error handling and modern .NET patterns.
- ✓ Look up BIN information for any payment card
- ✓ Handle all error cases gracefully
- ✓ Work with async/await patterns throughout
- ✓ Validate cards in a real payment flow
Prerequisites
- .NET 6.0 or higher
- Visual Studio 2022, VS Code, or JetBrains Rider
- Basic familiarity with async/await in C#
No additional NuGet packages are required for basic usage. The built-in System.Net.Http and System.Text.Json namespaces provide everything you need.
# Create a new console project to follow along
dotnet new console -n BinLookupDemo
cd BinLookupDemo
Step 1: Create Your Account
First, you’ll need a BINLookupAPI account to get your API key.
- 1 Go to app.binlookupapi.com/sign-in
- 2 Sign in with your Google account
- 3 An organisation is created automatically for you
You’ll start on the Development plan which includes 15,000 requests per month with mock responses — perfect for building and testing your integration.
Step 2: Create an API Key
Once logged in:
- 1 Navigate to API Keys in the dashboard
- 2 Click Create Key
- 3 Give it a name (e.g., "C# Development")
- 4 Copy the key immediately — it's only shown once
Your API key will look something like: blapi_live_xxxxxxxxxxxxxxxxxxxx
Store your API key securely. Never commit API keys to version control. Use environment variables or a secrets manager.
Step 3: Your First BIN Lookup
Let’s start with a simple example to verify everything works:
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
const string ApiKey = "your_api_key_here";
const string ApiUrl = "https://api.binlookupapi.com/v1/bin";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", ApiKey);
var requestBody = JsonSerializer.Serialize(new { number = 42467101 });
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = await client.PostAsync(ApiUrl, content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
Expected response:
{
"data": {
"bin": "42467101",
"scheme": "visa",
"funding": "debit",
"brand": "VISA",
"category": "CLASSIC",
"country": {
"code": "PL",
"name": "POLAND"
},
"issuer": {
"name": "ING BANK SLASKI SA",
"website": null,
"phone": null
},
"currency": "PLN",
"prepaid": false,
"commercial": false
}
}
Step 4: Production-Ready Code with Error Handling
The basic example above doesn’t handle errors properly. Here’s a production-ready implementation:
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace BinLookup;
#region Exceptions
/// <summary>
/// Base exception for all BIN lookup errors.
/// </summary>
public class BinLookupException : Exception
{
public BinLookupException(string message) : base(message) { }
public BinLookupException(string message, Exception inner) : base(message, inner) { }
}
/// <summary>
/// Raised when the BIN format is invalid.
/// </summary>
public class InvalidBinException : BinLookupException
{
public InvalidBinException(string message) : base(message) { }
}
/// <summary>
/// Raised when the API key is invalid, missing, or lacks permissions.
/// </summary>
public class AuthenticationException : BinLookupException
{
public AuthenticationException(string message) : base(message) { }
}
/// <summary>
/// Raised when the daily quota has been exceeded.
/// </summary>
public class QuotaExceededException : BinLookupException
{
public QuotaExceededException(string message) : base(message) { }
}
/// <summary>
/// Raised when the BIN is not found in the database.
/// </summary>
public class BinNotFoundException : BinLookupException
{
public BinNotFoundException(string message) : base(message) { }
}
/// <summary>
/// Raised when the API service encounters an error.
/// </summary>
public class ServiceException : BinLookupException
{
public ServiceException(string message) : base(message) { }
public ServiceException(string message, Exception inner) : base(message, inner) { }
}
#endregion
#region Models
/// <summary>
/// Represents issuer (bank) information.
/// </summary>
public sealed record Issuer(
[property: JsonPropertyName("name")] string? Name,
[property: JsonPropertyName("website")] string? Website,
[property: JsonPropertyName("phone")] string? Phone
);
/// <summary>
/// Represents country information.
/// </summary>
public sealed record Country(
[property: JsonPropertyName("code")] string Code,
[property: JsonPropertyName("name")] string Name
);
/// <summary>
/// Represents BIN information returned by the API.
/// </summary>
public sealed record BinInfo(
[property: JsonPropertyName("bin")] string Bin,
[property: JsonPropertyName("scheme")] string Scheme,
[property: JsonPropertyName("funding")] string Funding,
[property: JsonPropertyName("brand")] string? Brand,
[property: JsonPropertyName("category")] string? Category,
[property: JsonPropertyName("country")] Country Country,
[property: JsonPropertyName("issuer")] Issuer Issuer,
[property: JsonPropertyName("currency")] string? Currency,
[property: JsonPropertyName("prepaid")] bool Prepaid,
[property: JsonPropertyName("commercial")] bool Commercial
);
/// <summary>
/// API response wrapper.
/// </summary>
internal sealed record ApiResponse(
[property: JsonPropertyName("data")] BinInfo Data
);
/// <summary>
/// API error response.
/// </summary>
internal sealed record ApiErrorResponse(
[property: JsonPropertyName("message")] string? Message
);
/// <summary>
/// Quota information from response headers.
/// </summary>
public sealed record QuotaInfo(
int Limit,
int Remaining,
long ResetTimestamp
);
#endregion
#region Client
/// <summary>
/// Client for the BINLookupAPI service.
/// </summary>
public sealed class BinLookupClient : IDisposable
{
private const string BaseUrl = "https://api.binlookupapi.com/v1/bin";
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
/// <summary>
/// Initialize the client with an API key.
/// </summary>
/// <param name="apiKey">Your BINLookupAPI key. If null, reads from BINLOOKUP_API_KEY environment variable.</param>
/// <param name="httpClient">Optional HttpClient instance (for IHttpClientFactory usage).</param>
public BinLookupClient(string? apiKey = null, HttpClient? httpClient = null)
{
var key = apiKey ?? Environment.GetEnvironmentVariable("BINLOOKUP_API_KEY");
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException(
"API key required. Pass it directly or set BINLOOKUP_API_KEY environment variable.",
nameof(apiKey)
);
}
_httpClient = httpClient ?? new HttpClient();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", key);
_httpClient.Timeout = TimeSpan.FromSeconds(10);
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
}
/// <summary>
/// Look up information for a BIN.
/// </summary>
/// <param name="binNumber">The BIN to look up (4-8 digits).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>BinInfo object with card details.</returns>
/// <exception cref="InvalidBinException">If the BIN format is invalid.</exception>
/// <exception cref="AuthenticationException">If the API key is invalid.</exception>
/// <exception cref="QuotaExceededException">If the daily quota is exceeded.</exception>
/// <exception cref="BinNotFoundException">If the BIN is not found.</exception>
/// <exception cref="ServiceException">If the API service has an error.</exception>
public async Task<BinInfo> LookupAsync(int binNumber, CancellationToken cancellationToken = default)
{
// Validate input
if (binNumber < 1000 || binNumber > 99999999)
{
throw new InvalidBinException(
$"BIN must be between 1000 and 99999999, got: {binNumber}"
);
}
HttpResponseMessage response;
try
{
var requestBody = JsonSerializer.Serialize(new { number = binNumber });
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
response = await _httpClient.PostAsync(BaseUrl, content, cancellationToken);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
throw new ServiceException("Request timed out. Please try again.", ex);
}
catch (HttpRequestException ex)
{
throw new ServiceException("Could not connect to API. Check your network.", ex);
}
// Handle error responses
var statusCode = (int)response.StatusCode;
switch (response.StatusCode)
{
case HttpStatusCode.BadRequest: // 400
var errorJson = await response.Content.ReadAsStringAsync(cancellationToken);
var errorResponse = JsonSerializer.Deserialize<ApiErrorResponse>(errorJson, _jsonOptions);
throw new InvalidBinException(errorResponse?.Message ?? "Invalid BIN");
case HttpStatusCode.Unauthorized: // 401
throw new AuthenticationException("Invalid API key. Check your credentials.");
case HttpStatusCode.PaymentRequired: // 402
throw new AuthenticationException("No active subscription. Please subscribe to a plan.");
case HttpStatusCode.Forbidden: // 403
throw new AuthenticationException("API key lacks required permissions.");
case HttpStatusCode.NotFound: // 404
throw new BinNotFoundException($"BIN {binNumber} not found in database.");
case (HttpStatusCode)429: // Too Many Requests
throw new QuotaExceededException("Daily quota exceeded. Resets at midnight UTC.");
case HttpStatusCode.BadGateway: // 502
throw new ServiceException("API gateway error. Please try again later.");
}
if (statusCode >= 500)
{
throw new ServiceException(
$"API service error (HTTP {statusCode}). Please try again later."
);
}
if (!response.IsSuccessStatusCode)
{
throw new BinLookupException($"Unexpected error: HTTP {statusCode}");
}
// Parse successful response
var json = await response.Content.ReadAsStringAsync(cancellationToken);
var apiResponse = JsonSerializer.Deserialize<ApiResponse>(json, _jsonOptions);
return apiResponse?.Data ?? throw new BinLookupException("Invalid API response format");
}
/// <summary>
/// Get quota information from the API.
/// </summary>
public async Task<QuotaInfo?> GetQuotaInfoAsync(CancellationToken cancellationToken = default)
{
try
{
var requestBody = JsonSerializer.Serialize(new { number = 42467101 });
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(BaseUrl, content, cancellationToken);
if (!response.Headers.TryGetValues("X-Quota-Limit", out var limitValues) ||
!response.Headers.TryGetValues("X-Quota-Remaining", out var remainingValues) ||
!response.Headers.TryGetValues("X-Quota-Reset", out var resetValues))
{
return null;
}
return new QuotaInfo(
int.Parse(limitValues.First()),
int.Parse(remainingValues.First()),
long.Parse(resetValues.First())
);
}
catch
{
return null;
}
}
public void Dispose()
{
if (!_disposed)
{
_httpClient.Dispose();
_disposed = true;
}
}
}
#endregion
Step 5: Using the Client
Here’s how to use the production-ready client:
using BinLookup;
// Initialize with API key from environment
using var client = new BinLookupClient();
// Or pass the key directly
using var clientWithKey = new BinLookupClient(apiKey: "your_api_key_here");
// Look up a BIN
try
{
var info = await client.LookupAsync(42467101);
Console.WriteLine($"Card Network: {info.Scheme}");
Console.WriteLine($"Card Type: {info.Funding}");
Console.WriteLine($"Issuer: {info.Issuer.Name}");
Console.WriteLine($"Country: {info.Country.Name}");
Console.WriteLine($"Is Prepaid: {info.Prepaid}");
}
catch (InvalidBinException ex)
{
Console.WriteLine($"Invalid BIN: {ex.Message}");
}
catch (AuthenticationException ex)
{
Console.WriteLine($"Auth error: {ex.Message}");
// Check your API key
}
catch (QuotaExceededException ex)
{
Console.WriteLine($"Quota exceeded: {ex.Message}");
// Wait until midnight UTC or upgrade your plan
}
catch (BinNotFoundException ex)
{
Console.WriteLine($"BIN not found: {ex.Message}");
// This BIN isn't in the database
}
catch (ServiceException ex)
{
Console.WriteLine($"Service error: {ex.Message}");
// Retry with exponential backoff
}
catch (BinLookupException ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
The exception hierarchy lets you catch specific errors or use the base BinLookupException to handle all API-related errors at once.
Step 6: Using IHttpClientFactory (Recommended)
For ASP.NET Core applications, use IHttpClientFactory for proper connection management:
// In Program.cs or Startup.cs
builder.Services.AddHttpClient<BinLookupClient>(client =>
{
client.BaseAddress = new Uri("https://api.binlookupapi.com/v1/");
client.Timeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddSingleton(sp =>
{
var httpClient = sp.GetRequiredService<IHttpClientFactory>()
.CreateClient(nameof(BinLookupClient));
var apiKey = builder.Configuration["BinLookupApi:ApiKey"];
return new BinLookupClient(apiKey, httpClient);
});
Configuration in appsettings.json:
{
"BinLookupApi": {
"ApiKey": "your_api_key_here"
}
}
Using IHttpClientFactory prevents socket exhaustion issues and properly manages the lifetime of HttpClient instances in long-running applications.
Step 7: Real-World Example — Payment Form Validation
Here’s a practical example of using BIN lookup in a payment flow:
using BinLookup;
namespace PaymentProcessing;
/// <summary>
/// Result of card BIN validation.
/// </summary>
public sealed record CardValidationResult
{
public required bool Valid { get; init; }
public string? CardType { get; init; }
public string? Issuer { get; init; }
public string? Country { get; init; }
public bool IsPrepaid { get; init; }
public List<string>? Warnings { get; init; }
public string? Error { get; init; }
}
/// <summary>
/// Service for validating payment card BINs.
/// </summary>
public sealed class CardValidationService
{
private readonly BinLookupClient _binLookupClient;
public CardValidationService(BinLookupClient binLookupClient)
{
_binLookupClient = binLookupClient;
}
/// <summary>
/// Validate a card BIN for payment processing.
/// </summary>
/// <param name="cardNumber">The card number (only first 6-8 digits are used).</param>
/// <param name="blockPrepaid">If true, reject prepaid cards.</param>
/// <param name="allowedCountries">List of allowed country codes (e.g., ["US", "GB"]).</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>CardValidationResult with validation details.</returns>
public async Task<CardValidationResult> ValidateCardBinAsync(
string cardNumber,
bool blockPrepaid = false,
IReadOnlySet<string>? allowedCountries = null,
CancellationToken cancellationToken = default)
{
var warnings = new List<string>();
// Extract BIN (first 8 digits)
var digitsOnly = new string(cardNumber.Where(char.IsDigit).ToArray());
if (digitsOnly.Length < 6)
{
return new CardValidationResult
{
Valid = false,
Error = "Card number must be at least 6 digits"
};
}
var binNumber = int.Parse(digitsOnly[..Math.Min(8, digitsOnly.Length)]);
BinInfo info;
try
{
info = await _binLookupClient.LookupAsync(binNumber, cancellationToken);
}
catch (BinNotFoundException)
{
return new CardValidationResult
{
Valid = false,
Error = "Unable to identify card. Please check the number."
};
}
catch (QuotaExceededException)
{
// Fail open - allow the transaction but log the issue
return new CardValidationResult
{
Valid = true,
Warnings = ["BIN validation skipped due to quota limits"]
};
}
catch (BinLookupException ex)
{
// Fail open for service errors
return new CardValidationResult
{
Valid = true,
Warnings = [$"BIN validation unavailable: {ex.Message}"]
};
}
// Check prepaid status
if (blockPrepaid && info.Prepaid)
{
return new CardValidationResult
{
Valid = false,
CardType = info.Scheme,
Issuer = info.Issuer.Name,
Country = info.Country.Code,
IsPrepaid = true,
Error = "Prepaid cards are not accepted"
};
}
// Check country restrictions
if (allowedCountries is not null && !allowedCountries.Contains(info.Country.Code))
{
return new CardValidationResult
{
Valid = false,
CardType = info.Scheme,
Issuer = info.Issuer.Name,
Country = info.Country.Code,
Error = $"Cards from {info.Country.Name} are not accepted"
};
}
// Add warnings for high-risk indicators
if (info.Prepaid)
{
warnings.Add("This is a prepaid card");
}
if (info.Commercial)
{
warnings.Add("This is a corporate/business card");
}
return new CardValidationResult
{
Valid = true,
CardType = info.Scheme,
Issuer = info.Issuer.Name,
Country = info.Country.Code,
IsPrepaid = info.Prepaid,
Warnings = warnings.Count > 0 ? warnings : null
};
}
}
// Usage example
public static class Program
{
public static async Task Main()
{
using var client = new BinLookupClient();
var validationService = new CardValidationService(client);
var allowedCountries = new HashSet<string> { "US", "GB", "CA", "AU" };
var result = await validationService.ValidateCardBinAsync(
cardNumber: "4246710012345678",
blockPrepaid: true,
allowedCountries: allowedCountries
);
if (result.Valid)
{
Console.WriteLine($"Card accepted: {result.CardType} from {result.Issuer}");
if (result.Warnings is not null)
{
foreach (var warning in result.Warnings)
{
Console.WriteLine($"Warning: {warning}");
}
}
}
else
{
Console.WriteLine($"Card rejected: {result.Error}");
}
}
}
For payment validation, consider “failing open” when the API is unavailable. It’s usually better to allow a transaction and verify later than to block all payments during an outage.
Best Practices Summary
- ✓ Store API key in environment variable or IConfiguration
- ✓ Use IHttpClientFactory in ASP.NET Core applications
- ✓ Handle all error types: network, auth, quota, not-found
- ✓ Set request timeouts (10 seconds recommended)
- ✓ Implement retry logic with exponential backoff for transient errors
- ✓ Consider fail-open strategy for non-critical validation
- ✓ Monitor your quota usage via response headers
- ✓ Use CancellationToken for proper async cancellation support
Next Steps
- View the full API Reference for all available fields and error codes
- Explore pricing plans to find the right quota for your needs
- Contact support if you need help with your integration
Ready to get started? Create your free account and start building today.