← Back to Blog

BIN Lookup API for JavaScript: Integration Guide

Published: February 5, 2026
Tags: javascript, nodejs, tutorial, integration, developer

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.

What You'll Build
  • 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 fetch support)
  • Or any modern browser with fetch API support
  • TypeScript (optional, but recommended for type safety)

No external dependencies required! We use the native fetch API available in modern JavaScript environments.

i Node.js Version

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.

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., "JavaScript 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 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 {};
    }
  }
}
* CommonJS Support

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}`);
  }
}
* 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 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();
i Rate Limiting

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}`);
}
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.

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

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
  • Never expose API keys in client-side JavaScript

Next Steps

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