BIN Lookup API for Python: Complete Integration Guide
This guide walks you through integrating BINLookupAPI into your Python application, from creating your account to writing production-ready code with proper error handling.
- ✓ Look up BIN information for any payment card
- ✓ Handle all error cases gracefully
- ✓ Work with both synchronous and asynchronous code
- ✓ Validate cards in a real payment flow
Prerequisites
- Python 3.8 or higher
requestslibrary (for synchronous code)httpxlibrary (optional, for async code)
Install the required packages:
pip install requests
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., "Python 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.
Step 3: Your First BIN Lookup
Let’s start with a simple example to verify everything works:
import requests
API_KEY = "your_api_key_here"
API_URL = "https://api.binlookupapi.com/v1/bin"
def lookup_bin(bin_number: int) -> dict:
"""Look up a BIN and return card information."""
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={"number": bin_number}
)
response.raise_for_status()
return response.json()
# Test it out
result = lookup_bin(42467101)
print(result)
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:
import requests
from typing import Optional
from dataclasses import dataclass
from enum import Enum
import os
class BINLookupError(Exception):
"""Base exception for BIN lookup errors."""
pass
class InvalidBINError(BINLookupError):
"""Raised when the BIN format is invalid."""
pass
class AuthenticationError(BINLookupError):
"""Raised when the API key is invalid or missing."""
pass
class QuotaExceededError(BINLookupError):
"""Raised when the daily quota has been exceeded."""
pass
class BINNotFoundError(BINLookupError):
"""Raised when the BIN is not in the database."""
pass
class ServiceError(BINLookupError):
"""Raised when the API service encounters an error."""
pass
@dataclass
class Issuer:
name: Optional[str]
website: Optional[str]
phone: Optional[str]
@dataclass
class Country:
code: str
name: str
@dataclass
class BINInfo:
bin: str
scheme: str
funding: str
brand: Optional[str]
category: Optional[str]
country: Country
issuer: Issuer
currency: Optional[str]
prepaid: bool
commercial: bool
class BINLookupClient:
"""Client for the BINLookupAPI service."""
BASE_URL = "https://api.binlookupapi.com/v1/bin"
def __init__(self, api_key: Optional[str] = None):
"""
Initialize the client.
Args:
api_key: Your BINLookupAPI key. If not provided,
reads from BINLOOKUP_API_KEY environment variable.
"""
self.api_key = api_key or os.environ.get("BINLOOKUP_API_KEY")
if not self.api_key:
raise ValueError(
"API key required. Pass it directly or set "
"BINLOOKUP_API_KEY environment variable."
)
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
})
def lookup(self, bin_number: int) -> BINInfo:
"""
Look up information for a BIN.
Args:
bin_number: The BIN to look up (4-8 digits).
Returns:
BINInfo object with card details.
Raises:
InvalidBINError: If the BIN format is invalid.
AuthenticationError: If the API key is invalid.
QuotaExceededError: If the daily quota is exceeded.
BINNotFoundError: If the BIN is not found.
ServiceError: If the API service has an error.
"""
# Validate input
if not isinstance(bin_number, int) or not (1000 <= bin_number <= 99999999):
raise InvalidBINError(
f"BIN must be an integer between 1000 and 99999999, "
f"got: {bin_number}"
)
try:
response = self.session.post(
self.BASE_URL,
json={"number": bin_number},
timeout=10
)
except requests.exceptions.Timeout:
raise ServiceError("Request timed out. Please try again.")
except requests.exceptions.ConnectionError:
raise ServiceError("Could not connect to API. Check your network.")
# Handle error responses
if response.status_code == 400:
raise InvalidBINError(response.json().get("message", "Invalid BIN"))
elif response.status_code == 401:
raise AuthenticationError(
"Invalid API key. Check your credentials."
)
elif response.status_code == 402:
raise AuthenticationError(
"No active subscription. Please subscribe to a plan."
)
elif response.status_code == 403:
raise AuthenticationError(
"API key lacks required permissions."
)
elif response.status_code == 429:
raise QuotaExceededError(
"Daily quota exceeded. Resets at midnight UTC."
)
elif response.status_code == 404:
raise BINNotFoundError(f"BIN {bin_number} not found in database.")
elif response.status_code >= 500:
raise ServiceError(
f"API service error (HTTP {response.status_code}). "
"Please try again later."
)
elif not response.ok:
raise BINLookupError(
f"Unexpected error: HTTP {response.status_code}"
)
# Parse successful response
data = response.json()["data"]
return BINInfo(
bin=data["bin"],
scheme=data["scheme"],
funding=data["funding"],
brand=data.get("brand"),
category=data.get("category"),
country=Country(
code=data["country"]["code"],
name=data["country"]["name"]
),
issuer=Issuer(
name=data["issuer"].get("name"),
website=data["issuer"].get("website"),
phone=data["issuer"].get("phone")
),
currency=data.get("currency"),
prepaid=data["prepaid"],
commercial=data["commercial"]
)
def get_quota_info(self) -> dict:
"""
Get current quota information from the last request.
Returns:
Dict with quota_limit, quota_remaining, and quota_reset.
"""
# Make a test request to get headers
try:
response = self.session.post(
self.BASE_URL,
json={"number": 42467101},
timeout=10
)
return {
"quota_limit": int(response.headers.get("X-Quota-Limit", 0)),
"quota_remaining": int(response.headers.get("X-Quota-Remaining", 0)),
"quota_reset": int(response.headers.get("X-Quota-Reset", 0))
}
except Exception:
return {}
Step 5: Using the Client
Here’s how to use the production-ready client:
# Initialize with API key from environment
client = BINLookupClient()
# Or pass the key directly
client = BINLookupClient(api_key="your_api_key_here")
# Look up a BIN
try:
info = client.lookup(42467101)
print(f"Card Network: {info.scheme}")
print(f"Card Type: {info.funding}")
print(f"Issuer: {info.issuer.name}")
print(f"Country: {info.country.name}")
print(f"Is Prepaid: {info.prepaid}")
except InvalidBINError as e:
print(f"Invalid BIN: {e}")
except AuthenticationError as e:
print(f"Auth error: {e}")
# Check your API key
except QuotaExceededError as e:
print(f"Quota exceeded: {e}")
# Wait until midnight UTC or upgrade your plan
except BINNotFoundError as e:
print(f"BIN not found: {e}")
# This BIN isn't in the database
except ServiceError as e:
print(f"Service error: {e}")
# Retry with exponential backoff
except BINLookupError as e:
print(f"Unexpected error: {e}")
The exception hierarchy lets you catch specific errors or use the base BINLookupError to handle all API-related errors at once.
Step 6: Async Support (Optional)
For high-throughput applications, here’s an async version using httpx:
pip install httpx
import httpx
from typing import Optional
import asyncio
class AsyncBINLookupClient:
"""Async client for BINLookupAPI."""
BASE_URL = "https://api.binlookupapi.com/v1/bin"
def __init__(self, api_key: str):
self.api_key = api_key
self.client = httpx.AsyncClient(
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
timeout=10.0
)
async def lookup(self, bin_number: int) -> BINInfo:
"""Async BIN lookup."""
response = await self.client.post(
self.BASE_URL,
json={"number": bin_number}
)
if response.status_code == 401:
raise AuthenticationError("Invalid API key")
elif response.status_code == 404:
raise BINNotFoundError(f"BIN {bin_number} not found")
response.raise_for_status()
data = response.json()["data"]
return BINInfo(
bin=data["bin"],
scheme=data["scheme"],
funding=data["funding"],
brand=data.get("brand"),
category=data.get("category"),
country=Country(
code=data["country"]["code"],
name=data["country"]["name"]
),
issuer=Issuer(
name=data["issuer"].get("name"),
website=data["issuer"].get("website"),
phone=data["issuer"].get("phone")
),
currency=data.get("currency"),
prepaid=data["prepaid"],
commercial=data["commercial"]
)
async def lookup_many(self, bin_numbers: list[int]) -> list[BINInfo]:
"""Look up multiple BINs concurrently."""
tasks = [self.lookup(bin) for bin in bin_numbers]
return await asyncio.gather(*tasks, return_exceptions=True)
async def close(self):
"""Close the client connection."""
await self.client.aclose()
# Usage
async def main():
client = AsyncBINLookupClient("your_api_key")
try:
# Single lookup
info = await client.lookup(42467101)
print(f"Scheme: {info.scheme}")
# Multiple lookups in parallel
bins = [42467101, 51234567, 37123456]
results = await client.lookup_many(bins)
for result in results:
if isinstance(result, BINInfo):
print(f"{result.bin}: {result.scheme}")
else:
print(f"Error: {result}")
finally:
await client.close()
asyncio.run(main())
Step 7: Real-World Example — Payment Form Validation
Here’s a practical example of using BIN lookup in a payment flow:
from dataclasses import dataclass
from typing import Optional
@dataclass
class CardValidationResult:
valid: bool
card_type: Optional[str] = None
issuer: Optional[str] = None
country: Optional[str] = None
is_prepaid: bool = False
warnings: list[str] = None
error: Optional[str] = None
def validate_card_bin(
card_number: str,
client: BINLookupClient,
block_prepaid: bool = False,
allowed_countries: Optional[list[str]] = None
) -> CardValidationResult:
"""
Validate a card BIN for payment processing.
Args:
card_number: The card number (only first 6-8 digits are used)
client: BINLookupClient instance
block_prepaid: If True, reject prepaid cards
allowed_countries: List of allowed country codes (e.g., ['US', 'GB'])
Returns:
CardValidationResult with validation details
"""
warnings = []
# Extract BIN (first 8 digits)
digits_only = ''.join(filter(str.isdigit, card_number))
if len(digits_only) < 6:
return CardValidationResult(
valid=False,
error="Card number must be at least 6 digits"
)
bin_number = int(digits_only[:8])
try:
info = client.lookup(bin_number)
except BINNotFoundError:
return CardValidationResult(
valid=False,
error="Unable to identify card. Please check the number."
)
except QuotaExceededError:
# Fail open - allow the transaction but log the issue
return CardValidationResult(
valid=True,
warnings=["BIN validation skipped due to quota limits"]
)
except BINLookupError as e:
# Fail open for service errors
return CardValidationResult(
valid=True,
warnings=[f"BIN validation unavailable: {e}"]
)
# Check prepaid status
if block_prepaid and info.prepaid:
return CardValidationResult(
valid=False,
card_type=info.scheme,
issuer=info.issuer.name,
country=info.country.code,
is_prepaid=True,
error="Prepaid cards are not accepted"
)
# Check country restrictions
if allowed_countries and info.country.code not in allowed_countries:
return CardValidationResult(
valid=False,
card_type=info.scheme,
issuer=info.issuer.name,
country=info.country.code,
error=f"Cards from {info.country.name} are not accepted"
)
# Add warnings for high-risk indicators
if info.prepaid:
warnings.append("This is a prepaid card")
if info.commercial:
warnings.append("This is a corporate/business card")
return CardValidationResult(
valid=True,
card_type=info.scheme,
issuer=info.issuer.name,
country=info.country.code,
is_prepaid=info.prepaid,
warnings=warnings if warnings else None
)
# Usage example
client = BINLookupClient()
result = validate_card_bin(
card_number="4246710012345678",
client=client,
block_prepaid=True,
allowed_countries=["US", "GB", "CA", "AU"]
)
if result.valid:
print(f"Card accepted: {result.card_type} from {result.issuer}")
if result.warnings:
for warning in result.warnings:
print(f"Warning: {warning}")
else:
print(f"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 BINLOOKUP_API_KEY environment variable
- ✓ 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
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.