BIN Lookup API for JavaScript: Integration Guide
This guide walks you through integrating BINLookupAPI into your JavaScript application, from creating your account to writing production-ready code with proper error handling. Works with Node.js 18+ and modern browsers.
- ✓ Look up BIN information for any payment card
- ✓ Handle all error cases gracefully
- ✓ Work with both Promise-based and async/await code
- ✓ Validate cards in a real payment flow
Prerequisites
- Node.js 18 or higher (for native
fetchsupport) - Or any modern browser with
fetchAPI support - TypeScript (optional, but recommended for type safety)
No external dependencies required! We use the native fetch API available in modern JavaScript environments.
Node.js 18+ includes native fetch. For older versions, install node-fetch or undici.
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., "JavaScript 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 or expose them in client-side code.
Step 3: Your First BIN Lookup
Let’s start with a simple example to verify everything works:
const API_KEY = "your_api_key_here";
const API_URL = "https://api.binlookupapi.com/v1/bin";
async function lookupBin(binNumber) {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ number: binNumber })
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
// Test it out
const result = await lookupBin(42467101);
console.log(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 TypeScript types:
// types.ts - TypeScript interfaces (optional but recommended)
export interface Issuer {
name: string | null;
website: string | null;
phone: string | null;
}
export interface Country {
code: string;
name: string;
}
export interface BINInfo {
bin: string;
scheme: string;
funding: string;
brand: string | null;
category: string | null;
country: Country;
issuer: Issuer;
currency: string | null;
prepaid: boolean;
commercial: boolean;
}
export interface BINLookupResponse {
data: BINInfo;
}
// bin-lookup-client.js (ESM)
// Custom error classes
export class BINLookupError extends Error {
constructor(message, statusCode = null) {
super(message);
this.name = "BINLookupError";
this.statusCode = statusCode;
}
}
export class InvalidBINError extends BINLookupError {
constructor(message) {
super(message, 400);
this.name = "InvalidBINError";
}
}
export class AuthenticationError extends BINLookupError {
constructor(message, statusCode = 401) {
super(message, statusCode);
this.name = "AuthenticationError";
}
}
export class QuotaExceededError extends BINLookupError {
constructor(message) {
super(message, 429);
this.name = "QuotaExceededError";
}
}
export class BINNotFoundError extends BINLookupError {
constructor(message) {
super(message, 404);
this.name = "BINNotFoundError";
}
}
export class ServiceError extends BINLookupError {
constructor(message, statusCode = 500) {
super(message, statusCode);
this.name = "ServiceError";
}
}
/**
* Production-ready BIN Lookup API client
*/
export class BINLookupClient {
static BASE_URL = "https://api.binlookupapi.com/v1/bin";
/**
* Initialize the client.
* @param {string} [apiKey] - Your BINLookupAPI key. If not provided,
* reads from BINLOOKUP_API_KEY environment variable.
*/
constructor(apiKey = null) {
this.apiKey = apiKey || process.env.BINLOOKUP_API_KEY;
if (!this.apiKey) {
throw new Error(
"API key required. Pass it directly or set " +
"BINLOOKUP_API_KEY environment variable."
);
}
}
/**
* Look up information for a BIN.
* @param {number} binNumber - The BIN to look up (4-8 digits).
* @returns {Promise<BINInfo>} BINInfo object with card details.
* @throws {InvalidBINError} If the BIN format is invalid.
* @throws {AuthenticationError} If the API key is invalid.
* @throws {QuotaExceededError} If the daily quota is exceeded.
* @throws {BINNotFoundError} If the BIN is not found.
* @throws {ServiceError} If the API service has an error.
*/
async lookup(binNumber) {
// Validate input
if (
typeof binNumber !== "number" ||
!Number.isInteger(binNumber) ||
binNumber < 1000 ||
binNumber > 99999999
) {
throw new InvalidBINError(
`BIN must be an integer between 1000 and 99999999, got: ${binNumber}`
);
}
let response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
response = await fetch(BINLookupClient.BASE_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ number: binNumber }),
signal: controller.signal
});
clearTimeout(timeoutId);
} catch (error) {
if (error.name === "AbortError") {
throw new ServiceError("Request timed out. Please try again.");
}
throw new ServiceError("Could not connect to API. Check your network.");
}
// Handle error responses
if (response.status === 400) {
const body = await response.json().catch(() => ({}));
throw new InvalidBINError(body.message || "Invalid BIN");
}
if (response.status === 401) {
throw new AuthenticationError("Invalid API key. Check your credentials.");
}
if (response.status === 402) {
throw new AuthenticationError(
"No active subscription. Please subscribe to a plan.",
402
);
}
if (response.status === 403) {
throw new AuthenticationError(
"API key lacks required permissions.",
403
);
}
if (response.status === 429) {
throw new QuotaExceededError(
"Daily quota exceeded. Resets at midnight UTC."
);
}
if (response.status === 404) {
throw new BINNotFoundError(`BIN ${binNumber} not found in database.`);
}
if (response.status === 502) {
throw new ServiceError("Bad gateway. The API service is temporarily unavailable.", 502);
}
if (response.status >= 500) {
throw new ServiceError(
`API service error (HTTP ${response.status}). Please try again later.`,
response.status
);
}
if (!response.ok) {
throw new BINLookupError(
`Unexpected error: HTTP ${response.status}`,
response.status
);
}
// Parse successful response
const json = await response.json();
return json.data;
}
/**
* Get current quota information.
* @returns {Promise<{quotaLimit: number, quotaRemaining: number, quotaReset: number}>}
*/
async getQuotaInfo() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetch(BINLookupClient.BASE_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ number: 42467101 }),
signal: controller.signal
});
clearTimeout(timeoutId);
return {
quotaLimit: parseInt(response.headers.get("X-Quota-Limit") || "0", 10),
quotaRemaining: parseInt(response.headers.get("X-Quota-Remaining") || "0", 10),
quotaReset: parseInt(response.headers.get("X-Quota-Reset") || "0", 10)
};
} catch {
return {};
}
}
}
If you’re using CommonJS modules, replace export with module.exports and use require() for imports.
Here’s the CommonJS version:
// bin-lookup-client.cjs (CommonJS)
class BINLookupError extends Error {
constructor(message, statusCode = null) {
super(message);
this.name = "BINLookupError";
this.statusCode = statusCode;
}
}
class InvalidBINError extends BINLookupError {
constructor(message) {
super(message, 400);
this.name = "InvalidBINError";
}
}
// ... other error classes same as above ...
class BINLookupClient {
// ... same implementation as above ...
}
module.exports = {
BINLookupClient,
BINLookupError,
InvalidBINError,
AuthenticationError,
QuotaExceededError,
BINNotFoundError,
ServiceError
};
Step 5: Using the Client
Here’s how to use the production-ready client:
import {
BINLookupClient,
InvalidBINError,
AuthenticationError,
QuotaExceededError,
BINNotFoundError,
ServiceError,
BINLookupError
} from "./bin-lookup-client.js";
// Initialize with API key from environment
const client = new BINLookupClient();
// Or pass the key directly
const client = new BINLookupClient("your_api_key_here");
// Look up a BIN
try {
const info = await client.lookup(42467101);
console.log(`Card Network: ${info.scheme}`);
console.log(`Card Type: ${info.funding}`);
console.log(`Issuer: ${info.issuer.name}`);
console.log(`Country: ${info.country.name}`);
console.log(`Is Prepaid: ${info.prepaid}`);
} catch (error) {
if (error instanceof InvalidBINError) {
console.log(`Invalid BIN: ${error.message}`);
} else if (error instanceof AuthenticationError) {
console.log(`Auth error: ${error.message}`);
// Check your API key
} else if (error instanceof QuotaExceededError) {
console.log(`Quota exceeded: ${error.message}`);
// Wait until midnight UTC or upgrade your plan
} else if (error instanceof BINNotFoundError) {
console.log(`BIN not found: ${error.message}`);
// This BIN isn't in the database
} else if (error instanceof ServiceError) {
console.log(`Service error: ${error.message}`);
// Retry with exponential backoff
} else if (error instanceof BINLookupError) {
console.log(`Unexpected error: ${error.message}`);
}
}
The exception hierarchy lets you catch specific errors or use the base BINLookupError to handle all API-related errors at once.
Step 6: Async Patterns and Parallel Lookups
JavaScript’s async/await makes it easy to perform concurrent BIN lookups:
import { BINLookupClient, BINNotFoundError } from "./bin-lookup-client.js";
const client = new BINLookupClient();
/**
* Look up multiple BINs concurrently.
* @param {number[]} binNumbers - Array of BIN numbers to look up.
* @returns {Promise<Array<{bin: number, result: BINInfo | null, error: string | null}>>}
*/
async function lookupMany(binNumbers) {
const promises = binNumbers.map(async (bin) => {
try {
const result = await client.lookup(bin);
return { bin, result, error: null };
} catch (error) {
return { bin, result: null, error: error.message };
}
});
return Promise.all(promises);
}
/**
* Look up BINs with rate limiting.
* @param {number[]} binNumbers - Array of BIN numbers.
* @param {number} delayMs - Delay between requests in milliseconds.
*/
async function lookupWithRateLimit(binNumbers, delayMs = 100) {
const results = [];
for (const bin of binNumbers) {
try {
const result = await client.lookup(bin);
results.push({ bin, result, error: null });
} catch (error) {
results.push({ bin, result: null, error: error.message });
}
// Wait between requests to respect rate limits
await new Promise(resolve => setTimeout(resolve, delayMs));
}
return results;
}
// Usage
async function main() {
// Concurrent lookups (fast, but respect rate limits)
const bins = [42467101, 51234567, 37123456];
const results = await lookupMany(bins);
for (const { bin, result, error } of results) {
if (result) {
console.log(`${bin}: ${result.scheme} - ${result.issuer.name}`);
} else {
console.log(`${bin}: Error - ${error}`);
}
}
// Rate-limited lookups (safer for large batches)
const manyBins = [42467101, 51234567, 37123456, 60110012, 35281234];
const limitedResults = await lookupWithRateLimit(manyBins, 200);
console.log(`Processed ${limitedResults.length} BINs`);
}
main();
For high-volume lookups, consider implementing rate limiting to avoid hitting quota limits. The API returns X-Quota-Remaining headers you can use to adjust your request rate.
Step 7: Real-World Example — Payment Form Validation
Here’s a practical example of using BIN lookup in a payment flow:
import {
BINLookupClient,
BINNotFoundError,
QuotaExceededError,
BINLookupError
} from "./bin-lookup-client.js";
/**
* @typedef {Object} CardValidationResult
* @property {boolean} valid - Whether the card is valid for processing
* @property {string|null} cardType - Card network (visa, mastercard, etc.)
* @property {string|null} issuer - Issuing bank name
* @property {string|null} country - Country code
* @property {boolean} isPrepaid - Whether it's a prepaid card
* @property {string[]} warnings - Warning messages
* @property {string|null} error - Error message if invalid
*/
/**
* Validate a card BIN for payment processing.
* @param {string} cardNumber - The card number (only first 6-8 digits are used)
* @param {BINLookupClient} client - BINLookupClient instance
* @param {Object} options - Validation options
* @param {boolean} [options.blockPrepaid=false] - If true, reject prepaid cards
* @param {string[]} [options.allowedCountries] - List of allowed country codes
* @returns {Promise<CardValidationResult>}
*/
async function validateCardBin(cardNumber, client, options = {}) {
const { blockPrepaid = false, allowedCountries = null } = options;
const warnings = [];
// Extract BIN (first 8 digits)
const digitsOnly = cardNumber.replace(/\D/g, "");
if (digitsOnly.length < 6) {
return {
valid: false,
cardType: null,
issuer: null,
country: null,
isPrepaid: false,
warnings: [],
error: "Card number must be at least 6 digits"
};
}
const binNumber = parseInt(digitsOnly.slice(0, 8), 10);
let info;
try {
info = await client.lookup(binNumber);
} catch (error) {
if (error instanceof BINNotFoundError) {
return {
valid: false,
cardType: null,
issuer: null,
country: null,
isPrepaid: false,
warnings: [],
error: "Unable to identify card. Please check the number."
};
}
if (error instanceof QuotaExceededError) {
// Fail open - allow the transaction but log the issue
return {
valid: true,
cardType: null,
issuer: null,
country: null,
isPrepaid: false,
warnings: ["BIN validation skipped due to quota limits"],
error: null
};
}
if (error instanceof BINLookupError) {
// Fail open for service errors
return {
valid: true,
cardType: null,
issuer: null,
country: null,
isPrepaid: false,
warnings: [`BIN validation unavailable: ${error.message}`],
error: null
};
}
throw error;
}
// Check prepaid status
if (blockPrepaid && info.prepaid) {
return {
valid: false,
cardType: info.scheme,
issuer: info.issuer.name,
country: info.country.code,
isPrepaid: true,
warnings: [],
error: "Prepaid cards are not accepted"
};
}
// Check country restrictions
if (allowedCountries && !allowedCountries.includes(info.country.code)) {
return {
valid: false,
cardType: info.scheme,
issuer: info.issuer.name,
country: info.country.code,
isPrepaid: info.prepaid,
warnings: [],
error: `Cards from ${info.country.name} are not accepted`
};
}
// Add warnings for high-risk indicators
if (info.prepaid) {
warnings.push("This is a prepaid card");
}
if (info.commercial) {
warnings.push("This is a corporate/business card");
}
return {
valid: true,
cardType: info.scheme,
issuer: info.issuer.name,
country: info.country.code,
isPrepaid: info.prepaid,
warnings,
error: null
};
}
// Usage example
const client = new BINLookupClient();
const result = await validateCardBin(
"4246710012345678",
client,
{
blockPrepaid: true,
allowedCountries: ["US", "GB", "CA", "AU"]
}
);
if (result.valid) {
console.log(`Card accepted: ${result.cardType} from ${result.issuer}`);
if (result.warnings.length > 0) {
for (const warning of result.warnings) {
console.log(`Warning: ${warning}`);
}
}
} else {
console.log(`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.
Browser Usage
The client works in browsers too, but never expose your API key in client-side code. Instead, create a backend endpoint:
// Server-side (Node.js/Express)
import express from "express";
import { BINLookupClient } from "./bin-lookup-client.js";
const app = express();
const client = new BINLookupClient();
app.post("/api/validate-bin", express.json(), async (req, res) => {
const { bin } = req.body;
try {
const result = await client.lookup(bin);
res.json({ success: true, data: result });
} catch (error) {
res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
});
app.listen(3000);
// Client-side (browser)
async function lookupBin(bin) {
const response = await fetch("/api/validate-bin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bin })
});
return response.json();
}
// Usage in a payment form
const cardInput = document.getElementById("card-number");
cardInput.addEventListener("blur", async (e) => {
const cardNumber = e.target.value.replace(/\D/g, "");
if (cardNumber.length >= 6) {
const bin = parseInt(cardNumber.slice(0, 8), 10);
const result = await lookupBin(bin);
if (result.success) {
console.log(`Card type: ${result.data.scheme}`);
}
}
});
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
- ✓ Never expose API keys in client-side JavaScript
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.