BIN Lookup API for PHP: Complete Integration Guide
This guide will show you how to integrate BINLookupAPI into your PHP application, from the account creation to writing production ready code.
- ✓ Look up BIN information for any payment card
- ✓ Batch up to 2,000 BINs in a single request
- ✓ Handle all error cases gracefully
- ✓ Work with both cURL and Guzzle HTTP client
- ✓ Validate cards in a real payment flow
Prerequisites
- PHP 8.0 or higher
- cURL extension (built-in for most PHP installations)
- Guzzle HTTP client (optional, for more advanced usage)
Install Guzzle via Composer (optional):
composer require guzzlehttp/guzzle
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 free tier which includes 10 real lookups per day — no credit card required, so you can try the API before subscribing.
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., "PHP 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 using cURL to verify everything works:
<?php
$apiKey = 'your_api_key_here';
$apiUrl = 'https://api.binlookupapi.com/v1/bin';
function lookupBin(int $binNumber, string $apiKey, string $apiUrl): array
{
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['number' => $binNumber]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API request failed with status $httpCode");
}
return json_decode($response, true);
}
// Test it out
$result = lookupBin(42467101, $apiKey, $apiUrl);
print_r($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 with PSR-4 compatible class structure:
<?php
declare(strict_types=1);
namespace App\BINLookup;
/**
* Base exception for BIN lookup errors.
*/
class BINLookupException extends \Exception
{
}
/**
* Raised when the BIN format is invalid.
*/
class InvalidBINException extends BINLookupException
{
}
/**
* Raised when the API key is invalid or missing.
*/
class AuthenticationException extends BINLookupException
{
}
/**
* Raised when the daily quota has been exceeded.
*/
class QuotaExceededException extends BINLookupException
{
}
/**
* Raised when the BIN is not in the database.
*/
class BINNotFoundException extends BINLookupException
{
}
/**
* Raised when the API service encounters an error.
*/
class ServiceException extends BINLookupException
{
}
/**
* Data class for issuer information.
*/
class Issuer
{
public function __construct(
public readonly ?string $name,
public readonly ?string $website,
public readonly ?string $phone
) {
}
}
/**
* Data class for country information.
*/
class Country
{
public function __construct(
public readonly string $code,
public readonly string $name
) {
}
}
/**
* Data class for BIN information.
*/
class BINInfo
{
public function __construct(
public readonly string $bin,
public readonly string $scheme,
public readonly string $funding,
public readonly ?string $brand,
public readonly ?string $category,
public readonly Country $country,
public readonly Issuer $issuer,
public readonly ?string $currency,
public readonly bool $prepaid,
public readonly bool $commercial
) {
}
}
/**
* Client for the BINLookupAPI service.
*/
class BINLookupClient
{
private const BASE_URL = 'https://api.binlookupapi.com/v1/bin';
private const TIMEOUT = 10;
private string $apiKey;
/**
* Initialize the client.
*
* @param string|null $apiKey Your BINLookupAPI key. If not provided,
* reads from BINLOOKUP_API_KEY environment variable.
* @throws \InvalidArgumentException If no API key is provided or found.
*/
public function __construct(?string $apiKey = null)
{
$this->apiKey = $apiKey ?? getenv('BINLOOKUP_API_KEY') ?: ($_ENV['BINLOOKUP_API_KEY'] ?? '');
if (empty($this->apiKey)) {
throw new \InvalidArgumentException(
'API key required. Pass it directly or set BINLOOKUP_API_KEY environment variable.'
);
}
}
/**
* Look up information for a BIN.
*
* @param int $binNumber The BIN to look up (6-11 digits).
* @return BINInfo BINInfo object with card details.
* @throws InvalidBINException If the BIN format is invalid.
* @throws AuthenticationException If the API key is invalid.
* @throws QuotaExceededException If the daily quota is exceeded.
* @throws BINNotFoundException If the BIN is not found.
* @throws ServiceException If the API service has an error.
*/
public function lookup(int $binNumber): BINInfo
{
// Validate input
if ($binNumber < 100000 || $binNumber > 99999999999) {
throw new InvalidBINException(
"BIN must be an integer between 100000 and 99999999999, got: $binNumber"
);
}
$ch = curl_init(self::BASE_URL);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => self::TIMEOUT,
CURLOPT_CONNECTTIMEOUT => self::TIMEOUT,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['number' => $binNumber]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$curlErrno = curl_errno($ch);
curl_close($ch);
// Handle cURL errors
if ($curlErrno !== 0) {
if ($curlErrno === CURLE_OPERATION_TIMEDOUT) {
throw new ServiceException('Request timed out. Please try again.');
}
throw new ServiceException("Could not connect to API: $curlError");
}
// Handle error responses
$responseData = json_decode($response, true);
switch ($httpCode) {
case 200:
break; // Success, continue processing
case 400:
throw new InvalidBINException(
$responseData['message'] ?? 'Invalid BIN'
);
case 401:
throw new AuthenticationException(
'Invalid API key. Check your credentials.'
);
case 403:
throw new AuthenticationException(
'API key lacks required permissions.'
);
case 404:
throw new BINNotFoundException(
"BIN $binNumber not found in database."
);
case 429:
throw new QuotaExceededException(
'Daily quota exceeded. Resets at midnight UTC.'
);
case 502:
throw new ServiceException(
'API gateway error. Please try again later.'
);
default:
if ($httpCode >= 500) {
throw new ServiceException(
"API service error (HTTP $httpCode). Please try again later."
);
}
throw new BINLookupException(
"Unexpected error: HTTP $httpCode"
);
}
// Parse successful response
$data = $responseData['data'];
return new BINInfo(
bin: $data['bin'],
scheme: $data['scheme'],
funding: $data['funding'],
brand: $data['brand'] ?? null,
category: $data['category'] ?? null,
country: new Country(
code: $data['country']['code'],
name: $data['country']['name']
),
issuer: new Issuer(
name: $data['issuer']['name'] ?? null,
website: $data['issuer']['website'] ?? null,
phone: $data['issuer']['phone'] ?? null
),
currency: $data['currency'] ?? null,
prepaid: $data['prepaid'],
commercial: $data['commercial']
);
}
}
Step 5: Using the Client
Here’s how to use the production-ready client:
<?php
use App\BINLookup\BINLookupClient;
use App\BINLookup\InvalidBINException;
use App\BINLookup\AuthenticationException;
use App\BINLookup\QuotaExceededException;
use App\BINLookup\BINNotFoundException;
use App\BINLookup\ServiceException;
use App\BINLookup\BINLookupException;
// Initialize with API key from environment
$client = new BINLookupClient();
// Or pass the key directly
$client = new BINLookupClient('your_api_key_here');
// Look up a BIN
try {
$info = $client->lookup(42467101);
echo "Card Network: {$info->scheme}\n";
echo "Card Type: {$info->funding}\n";
echo "Issuer: {$info->issuer->name}\n";
echo "Country: {$info->country->name}\n";
echo "Is Prepaid: " . ($info->prepaid ? 'Yes' : 'No') . "\n";
} catch (InvalidBINException $e) {
echo "Invalid BIN: {$e->getMessage()}\n";
} catch (AuthenticationException $e) {
echo "Auth error: {$e->getMessage()}\n";
// Check your API key
} catch (QuotaExceededException $e) {
echo "Quota exceeded: {$e->getMessage()}\n";
// Wait until midnight UTC or upgrade your plan
} catch (BINNotFoundException $e) {
echo "BIN not found: {$e->getMessage()}\n";
// This BIN isn't in the database
} catch (ServiceException $e) {
echo "Service error: {$e->getMessage()}\n";
// Retry with exponential backoff
} catch (BINLookupException $e) {
echo "Unexpected error: {$e->getMessage()}\n";
}
The exception hierarchy lets you catch specific errors or use the base BINLookupException to handle all API-related errors at once.
Step 6: Batch Lookups
If you need to enrich a whole dataset — a transaction export, a chargeback report, a card-on-file migration — looping over the single-lookup endpoint is the slow way. The API has a native batch endpoint:
POST https://api.binlookupapi.com/v1/bins
It accepts between 1 and 2,000 BINs per request. The body is a single numbers array, and each entry follows the same rules as a single lookup — 6 to 11 digits, as an integer or a digit string:
{
"numbers": [400062, "400597", 99999999]
}
The response contains one result per submitted number, in the same order:
{
"mode": "live",
"results": [
{
"number": 400062,
"data": {
"bin": "400062",
"scheme": "visa",
"funding": "debit",
"country": { "code": "AE", "name": "UNITED ARAB EMIRATES" },
"issuer": { "name": "EMIRATES NBD BANK (P.J.S.C.)" }
},
"meta": { "longer_bin_available": true }
},
{
"number": "400597",
"data": {
"bin": "400597",
"scheme": "visa",
"funding": "credit",
"category": "BUSINESS",
"country": { "code": "US", "name": "UNITED STATES" },
"issuer": { "name": "FIRST ELECTRONIC BANK" },
"commercial": true
},
"meta": { "longer_bin_available": true }
},
{
"number": 99999999,
"error": "NOT_FOUND",
"message": "BIN not found"
}
]
}
The data objects contain the same fields as a single lookup response — trimmed here for brevity.
Note the third entry: an invalid or unknown BIN fails individually without affecting the rest of the batch. Each failed item carries an error code (NOT_FOUND or BAD_REQUEST) and a human-readable message. The whole request only fails for request-level problems: malformed JSON, a missing or oversized numbers array, authentication, or quota.
One safety detail worth knowing: values longer than 11 digits — possible full card numbers — come back as "[REDACTED]" with a per-item BAD_REQUEST, and are not stored in request logs.
Batch lookups are available on the BLAPI-500KPM plan ($100/mo). Test-mode organisations always have access, so you can build against the endpoint for free (mock data, 500 lookups/day). Every submitted number counts as one lookup — including invalid and unknown ones. If a batch is larger than your remaining daily quota, the request returns 429 and consumes nothing; the X-Quota-Remaining header reports your actual remaining quota.
Here’s a lookupBatch() method for the BINLookupClient class from Step 4. It splits larger lists into chunks of 2,000 automatically:
<?php
declare(strict_types=1);
namespace App\BINLookup;
/**
* A single item from a batch lookup response.
*/
class BatchResult
{
public function __construct(
public readonly int|string $number,
public readonly ?BINInfo $info = null,
public readonly ?string $error = null,
public readonly ?string $message = null
) {
}
public function isSuccess(): bool
{
return $this->info !== null;
}
}
class BINLookupClient
{
private const BATCH_URL = 'https://api.binlookupapi.com/v1/bins';
private const MAX_BATCH_SIZE = 2000;
// ... constructor and lookup() from Step 4 ...
/**
* Look up multiple BINs in one or more batch requests.
*
* Lists larger than 2,000 BINs are split into multiple
* requests automatically.
*
* @param array<int|string> $numbers BINs to look up (6-11 digits each).
* @return BatchResult[] One result per submitted number, in the same order.
* @throws AuthenticationException If the API key is invalid.
* @throws QuotaExceededException If the batch exceeds the remaining daily quota.
* @throws ServiceException If the API service has an error.
*/
public function lookupBatch(array $numbers): array
{
$results = [];
foreach (array_chunk($numbers, self::MAX_BATCH_SIZE) as $chunk) {
$ch = curl_init(self::BATCH_URL);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => self::TIMEOUT,
CURLOPT_CONNECTTIMEOUT => self::TIMEOUT,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['numbers' => $chunk]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$curlErrno = curl_errno($ch);
curl_close($ch);
if ($curlErrno !== 0) {
throw new ServiceException("Could not connect to API: $curlError");
}
$responseData = json_decode($response, true);
if ($httpCode !== 200) {
match (true) {
$httpCode === 400 => throw new InvalidBINException(
$responseData['message'] ?? 'Invalid request body'
),
$httpCode === 401 => throw new AuthenticationException(
'Invalid API key. Check your credentials.'
),
$httpCode === 403 => throw new AuthenticationException(
'API key lacks required permissions.'
),
$httpCode === 429 => throw new QuotaExceededException(
'Batch exceeds remaining daily quota. No lookups were consumed — check the X-Quota-Remaining header.'
),
$httpCode >= 500 => throw new ServiceException(
"API service error (HTTP $httpCode). Please try again later."
),
default => throw new BINLookupException(
"Unexpected error: HTTP $httpCode"
),
};
}
foreach ($responseData['results'] as $item) {
if (isset($item['error'])) {
$results[] = new BatchResult(
number: $item['number'],
error: $item['error'],
message: $item['message'] ?? null
);
continue;
}
$results[] = new BatchResult(
number: $item['number'],
info: $this->binInfoFromArray($item['data'])
);
}
}
return $results;
}
/**
* Map a decoded `data` object to a BINInfo instance.
* (The same mapping lookup() performs — refactor it to reuse this.)
*/
private function binInfoFromArray(array $data): BINInfo
{
return new BINInfo(
bin: $data['bin'],
scheme: $data['scheme'],
funding: $data['funding'],
brand: $data['brand'] ?? null,
category: $data['category'] ?? null,
country: new Country(
code: $data['country']['code'],
name: $data['country']['name']
),
issuer: new Issuer(
name: $data['issuer']['name'] ?? null,
website: $data['issuer']['website'] ?? null,
phone: $data['issuer']['phone'] ?? null
),
currency: $data['currency'] ?? null,
prepaid: $data['prepaid'],
commercial: $data['commercial']
);
}
}
Using it:
<?php
$client = new BINLookupClient();
$results = $client->lookupBatch([400062, '400597', 99999999]);
foreach ($results as $result) {
if ($result->isSuccess()) {
$info = $result->info;
echo "{$result->number}: {$info->scheme} {$info->funding} — "
. "{$info->issuer->name} ({$info->country->code})\n";
} else {
echo "{$result->number}: {$result->error} — {$result->message}\n";
}
}
Output:
400062: visa debit — EMIRATES NBD BANK (P.J.S.C.) (AE)
400597: visa credit — FIRST ELECTRONIC BANK (US)
99999999: NOT_FOUND — BIN not found
For the full request/response reference — headers, error codes, and limits — see the batch lookups documentation.
Step 7: Guzzle HTTP Client (Optional)
For applications already using Guzzle, here’s an alternative implementation. It also includes a lookupMany() method that fires concurrent single lookups — a useful fallback for bulk work on plans that don’t include the native batch endpoint from Step 6:
composer require guzzlehttp/guzzle
<?php
declare(strict_types=1);
namespace App\BINLookup;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RequestOptions;
/**
* Guzzle-based client for BINLookupAPI.
*/
class GuzzleBINLookupClient
{
private const BASE_URL = 'https://api.binlookupapi.com/v1/bin';
private Client $client;
public function __construct(?string $apiKey = null)
{
$apiKey = $apiKey ?? getenv('BINLOOKUP_API_KEY') ?: ($_ENV['BINLOOKUP_API_KEY'] ?? '');
if (empty($apiKey)) {
throw new \InvalidArgumentException(
'API key required. Pass it directly or set BINLOOKUP_API_KEY environment variable.'
);
}
$this->client = new Client([
'base_uri' => self::BASE_URL,
'timeout' => 10.0,
'headers' => [
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
],
]);
}
public function lookup(int $binNumber): BINInfo
{
if ($binNumber < 100000 || $binNumber > 99999999999) {
throw new InvalidBINException(
"BIN must be an integer between 100000 and 99999999999, got: $binNumber"
);
}
try {
$response = $this->client->post(self::BASE_URL, [
RequestOptions::JSON => ['number' => $binNumber],
]);
} catch (ConnectException $e) {
throw new ServiceException('Could not connect to API. Check your network.');
} catch (RequestException $e) {
$this->handleRequestException($e, $binNumber);
}
$responseData = json_decode($response->getBody()->getContents(), true);
$data = $responseData['data'];
return new BINInfo(
bin: $data['bin'],
scheme: $data['scheme'],
funding: $data['funding'],
brand: $data['brand'] ?? null,
category: $data['category'] ?? null,
country: new Country(
code: $data['country']['code'],
name: $data['country']['name']
),
issuer: new Issuer(
name: $data['issuer']['name'] ?? null,
website: $data['issuer']['website'] ?? null,
phone: $data['issuer']['phone'] ?? null
),
currency: $data['currency'] ?? null,
prepaid: $data['prepaid'],
commercial: $data['commercial']
);
}
private function handleRequestException(RequestException $e, int $binNumber): never
{
$response = $e->getResponse();
if ($response === null) {
throw new ServiceException('Request failed: ' . $e->getMessage());
}
$httpCode = $response->getStatusCode();
$body = json_decode($response->getBody()->getContents(), true);
match ($httpCode) {
400 => throw new InvalidBINException($body['message'] ?? 'Invalid BIN'),
401 => throw new AuthenticationException('Invalid API key. Check your credentials.'),
403 => throw new AuthenticationException('API key lacks required permissions.'),
404 => throw new BINNotFoundException("BIN $binNumber not found in database."),
429 => throw new QuotaExceededException('Daily quota exceeded. Resets at midnight UTC.'),
502 => throw new ServiceException('API gateway error. Please try again later.'),
default => $httpCode >= 500
? throw new ServiceException("API service error (HTTP $httpCode). Please try again later.")
: throw new BINLookupException("Unexpected error: HTTP $httpCode")
};
}
/**
* Look up multiple BINs with concurrent single-lookup requests.
*
* Fallback for plans without access to the native batch endpoint
* (POST /v1/bins) — each BIN here is a separate HTTP request.
*
* @param int[] $binNumbers Array of BIN numbers to look up.
* @return array<int, BINInfo|BINLookupException> Results indexed by BIN number.
*/
public function lookupMany(array $binNumbers): array
{
$promises = [];
foreach ($binNumbers as $binNumber) {
$promises[$binNumber] = $this->client->postAsync(self::BASE_URL, [
RequestOptions::JSON => ['number' => $binNumber],
]);
}
$results = [];
foreach ($promises as $binNumber => $promise) {
try {
$response = $promise->wait();
$responseData = json_decode($response->getBody()->getContents(), true);
$data = $responseData['data'];
$results[$binNumber] = new BINInfo(
bin: $data['bin'],
scheme: $data['scheme'],
funding: $data['funding'],
brand: $data['brand'] ?? null,
category: $data['category'] ?? null,
country: new Country(
code: $data['country']['code'],
name: $data['country']['name']
),
issuer: new Issuer(
name: $data['issuer']['name'] ?? null,
website: $data['issuer']['website'] ?? null,
phone: $data['issuer']['phone'] ?? null
),
currency: $data['currency'] ?? null,
prepaid: $data['prepaid'],
commercial: $data['commercial']
);
} catch (\Exception $e) {
$results[$binNumber] = new BINLookupException($e->getMessage());
}
}
return $results;
}
}
// Usage
$client = new GuzzleBINLookupClient('your_api_key');
// Single lookup
$info = $client->lookup(42467101);
echo "Scheme: {$info->scheme}\n";
// Multiple lookups in parallel (fallback when batch lookups aren't on your plan)
$bins = [42467101, 51234567, 37123456];
$results = $client->lookupMany($bins);
foreach ($results as $bin => $result) {
if ($result instanceof BINInfo) {
echo "$bin: {$result->scheme}\n";
} else {
echo "$bin: Error - {$result->getMessage()}\n";
}
}
Step 8: Real-World Example — Payment Form Validation
Here’s a practical example of using BIN lookup in a payment flow:
<?php
declare(strict_types=1);
namespace App\Payment;
use App\BINLookup\BINLookupClient;
use App\BINLookup\BINNotFoundException;
use App\BINLookup\QuotaExceededException;
use App\BINLookup\BINLookupException;
class CardValidationResult
{
public function __construct(
public readonly bool $valid,
public readonly ?string $cardType = null,
public readonly ?string $issuer = null,
public readonly ?string $country = null,
public readonly bool $isPrepaid = false,
public readonly ?array $warnings = null,
public readonly ?string $error = null
) {
}
}
class PaymentValidator
{
public function __construct(
private BINLookupClient $client
) {
}
/**
* Validate a card BIN for payment processing.
*
* @param string $cardNumber The card number (only first 6-8 digits are used).
* @param bool $blockPrepaid If true, reject prepaid cards.
* @param array|null $allowedCountries List of allowed country codes (e.g., ['US', 'GB']).
* @return CardValidationResult Validation result with details.
*/
public function validateCardBin(
string $cardNumber,
bool $blockPrepaid = false,
?array $allowedCountries = null
): CardValidationResult {
$warnings = [];
// Extract BIN (first 8 digits)
$digitsOnly = preg_replace('/\D/', '', $cardNumber);
if (strlen($digitsOnly) < 6) {
return new CardValidationResult(
valid: false,
error: 'Card number must be at least 6 digits'
);
}
$binNumber = (int) substr($digitsOnly, 0, 8);
try {
$info = $this->client->lookup($binNumber);
} 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 $e) {
// Fail open for service errors
return new CardValidationResult(
valid: true,
warnings: ["BIN validation unavailable: {$e->getMessage()}"]
);
}
// 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 !== null && !in_array($info->country->code, $allowedCountries, true)) {
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[] = 'This is a prepaid card';
}
if ($info->commercial) {
$warnings[] = '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: !empty($warnings) ? $warnings : null
);
}
}
// Usage example
$client = new BINLookupClient();
$validator = new PaymentValidator($client);
$result = $validator->validateCardBin(
cardNumber: '4246710012345678',
blockPrepaid: true,
allowedCountries: ['US', 'GB', 'CA', 'AU']
);
if ($result->valid) {
echo "Card accepted: {$result->cardType} from {$result->issuer}\n";
if ($result->warnings) {
foreach ($result->warnings as $warning) {
echo "Warning: $warning\n";
}
}
} else {
echo "Card rejected: {$result->error}\n";
}
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
- ✓ Use the batch endpoint (POST /v1/bins) for bulk lookups — up to 2,000 BINs per request
- ✓ 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
- Read the batch lookups docs for full details on
POST /v1/bins - 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.