BIN Lookup API for JavaScript: Integration Guide
This guide will teach you how to incorporate BINLookupAPI into your JavaScript application; compatible with Node.js 18+ and modern browsers.
- ✓ Look up BIN information for any payment card
- ✓ Handle all error cases gracefully
- ✓ Look up thousands of BINs at once with the batch endpoint
- ✓ 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 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., "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 (6-11 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 < 100000 ||
binNumber > 99999999999
) {
throw new InvalidBINError(
`BIN must be an integer between 100000 and 99999999999, 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 === 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: Batch and Parallel Lookups
If you need to look up more than a handful of BINs, the API has a native batch endpoint: POST https://api.binlookupapi.com/v1/bins (note the plural). It accepts 1 to 2,000 BINs per request, and each entry follows the same rules as a single lookup — 6–11 digits, as an integer or a digit string.
Let’s add a lookupBatch method to the BINLookupClient class from Step 4:
// Add inside the BINLookupClient class in bin-lookup-client.js
static BATCH_URL = "https://api.binlookupapi.com/v1/bins";
static MAX_BATCH_SIZE = 2000;
/**
* Look up multiple BINs in a single request using the batch endpoint.
* Arrays larger than 2,000 entries are split into multiple requests.
* @param {Array<number|string>} numbers - BINs to look up (6-11 digits each,
* integer or digit string).
* @returns {Promise<Array<Object>>} One item per submitted number, in the
* same order. Successful items have `data`
* and `meta`; failed items have `error`
* and `message`.
* @throws {InvalidBINError} If the request itself is malformed.
* @throws {AuthenticationError} If the API key is invalid or lacks access.
* @throws {QuotaExceededError} If the batch exceeds the remaining daily quota.
* @throws {ServiceError} If the API service has an error.
*/
async lookupBatch(numbers) {
const results = [];
// Split into chunks of 2,000 — the per-request maximum
for (let i = 0; i < numbers.length; i += BINLookupClient.MAX_BATCH_SIZE) {
const chunk = numbers.slice(i, i + BINLookupClient.MAX_BATCH_SIZE);
let response;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
response = await fetch(BINLookupClient.BATCH_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ numbers: chunk }),
signal: controller.signal
});
clearTimeout(timeoutId);
} catch (error) {
if (error.name === "AbortError") {
throw new ServiceError("Batch request timed out. Please try again.");
}
throw new ServiceError("Could not connect to API. Check your network.");
}
if (response.status === 400) {
const body = await response.json().catch(() => ({}));
throw new InvalidBINError(body.message || "Invalid batch request");
}
if (response.status === 401) {
throw new AuthenticationError("Invalid API key. Check your credentials.");
}
if (response.status === 403) {
throw new AuthenticationError(
"API key lacks required permissions.",
403
);
}
if (response.status === 429) {
const remaining = response.headers.get("X-Quota-Remaining");
throw new QuotaExceededError(
`Batch exceeds remaining daily quota (${remaining} lookups left). ` +
"Nothing was consumed — retry with a smaller batch."
);
}
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
);
}
const json = await response.json();
results.push(...json.results);
}
return results;
}
Using it is straightforward — iterate results and branch on error vs data:
import { BINLookupClient } from "./bin-lookup-client.js";
const client = new BINLookupClient();
// Integers and digit strings both work, same as single lookups
const results = await client.lookupBatch([400062, "400597", 99999999]);
for (const item of results) {
if (item.error) {
console.log(`${item.number}: ${item.error} - ${item.message}`);
} else {
console.log(
`${item.number}: ${item.data.scheme} ${item.data.funding} - ` +
`${item.data.issuer.name} (${item.data.country.code})`
);
}
}
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
The raw response looks like this — one result per submitted number, in the same order you sent them:
{
"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"
}
]
}
Each data object contains the same fields as a single lookup response (Step 3). A few things worth knowing:
- Per-item failures don’t break the batch. An unknown or invalid BIN comes back as an item with
error(NOT_FOUNDorBAD_REQUEST) and amessage— every other item is unaffected. The whole request only fails for request-level problems: bad JSON, a missing or oversized array, authentication, or quota. - Full card numbers are redacted. Any value longer than 11 digits (a possible full card number) is echoed back as
"[REDACTED]"with a per-itemBAD_REQUESTerror, and is never stored in request logs.
See the batch lookups documentation for full details.
Batch lookups are available on the BLAPI-500KPM plan ($100/mo) only — but test-mode organisations always have access, with mock data and a 500-lookups/day limit, so you can build against the endpoint for free. Every submitted number counts as one lookup against your quota, 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 tells you how many lookups you actually have left.
Fallback: Concurrent Single Lookups
If your plan doesn’t include batch lookups, JavaScript’s async/await still makes it easy to perform concurrent single 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();
When making many single 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. If you’re regularly processing large lists, the batch endpoint above is the better tool for the job.
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
- ✓ Use the batch endpoint (up to 2,000 BINs per request) for bulk lookups
- ✓ 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.