← Back to Blog

BIN Lookup API for Python: Complete Integration Guide

Published: February 5, 2026
Tags: python, tutorial, integration, developer

This guide walks you through integrating BINLookupAPI into your Python application, from creating your account to writing production-ready code with proper error handling.

What You'll Build
  • 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
  • requests library (for synchronous code)
  • httpx library (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.

Account Setup
  1. 1 Go to app.binlookupapi.com/sign-in
  2. 2 Sign in with your Google account
  3. 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. 1 Navigate to API Keys in the dashboard
  2. 2 Click Create Key
  3. 3 Give it a name (e.g., "Python Development")
  4. 4 Copy the key immediately — it's only shown once

Your API key will look something like: blapi_live_xxxxxxxxxxxxxxxxxxxx

! Security

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}")
* Error Handling Pattern

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}")
i Fail Open Strategy

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

Production Checklist
  • 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

Ready to get started? Create your free account and start building today.