← Back to Blog

BIN Lookup API with cURL: Integration Guide

Published: February 5, 2026
Tags: curl, bash, tutorial, integration, developer, cli

This guide walks you through integrating BINLookupAPI using cURL and Bash scripts, from creating your account to writing production-ready shell scripts with proper error handling.

What You'll Build
  • Look up BIN information using cURL commands
  • Create reusable shell script wrapper functions
  • Parse JSON responses with jq
  • Handle all error cases gracefully
  • Process multiple BINs in batch operations
  • Validate cards in a real payment flow script

Prerequisites

  • Bash 4.0 or higher (or zsh)
  • cURL (installed by default on most Unix systems)
  • jq (for JSON parsing)

Install jq if you don’t have it:

# macOS
brew install jq

# Ubuntu/Debian
sudo apt-get install jq

# CentOS/RHEL
sudo yum install jq

# Alpine
apk add jq

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., "CLI 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 in an environment variable. Never hardcode API keys in scripts or commit them to version control.

Set up your API key as an environment variable:

# Add to your ~/.bashrc, ~/.zshrc, or ~/.profile
export BINLOOKUP_API_KEY="blapi_live_xxxxxxxxxxxxxxxxxxxx"

# Reload your shell configuration
source ~/.bashrc  # or ~/.zshrc

Step 3: Your First BIN Lookup

Let’s start with a simple cURL command to verify everything works:

curl -X POST https://api.binlookupapi.com/v1/bin \
  -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"number": 42467101}'

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
  }
}

To pretty-print the response, pipe it through jq:

curl -s -X POST https://api.binlookupapi.com/v1/bin \
  -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"number": 42467101}' | jq .
* Silent Mode

The -s flag silences cURL’s progress output, making it easier to pipe the response to jq or other tools.

Step 4: Shell Script Wrapper Function

The basic cURL command above doesn’t handle errors properly. Here’s a production-ready shell function:

#!/usr/bin/env bash

# BIN Lookup API wrapper function
# Requires: BINLOOKUP_API_KEY environment variable

BINLOOKUP_API_URL="https://api.binlookupapi.com/v1/bin"

lookup_bin() {
    local bin_number="$1"
    local response
    local http_code
    local body

    # Validate input
    if [[ -z "$bin_number" ]]; then
        echo "Error: BIN number required" >&2
        return 1
    fi

    if [[ ! "$bin_number" =~ ^[0-9]{4,8}$ ]]; then
        echo "Error: BIN must be 4-8 digits" >&2
        return 1
    fi

    # Check for API key
    if [[ -z "$BINLOOKUP_API_KEY" ]]; then
        echo "Error: BINLOOKUP_API_KEY environment variable not set" >&2
        return 1
    fi

    # Make the API request and capture both body and HTTP status code
    response=$(curl -s -w "\n%{http_code}" -X POST "$BINLOOKUP_API_URL" \
        -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"number\": $bin_number}" \
        --connect-timeout 10 \
        --max-time 30)

    # Extract HTTP status code (last line) and body (everything else)
    http_code=$(echo "$response" | tail -n1)
    body=$(echo "$response" | sed '$d')

    # Handle HTTP status codes
    case "$http_code" in
        200)
            echo "$body"
            return 0
            ;;
        400)
            echo "Error: Invalid BIN format - $(echo "$body" | jq -r '.message // "Bad request"')" >&2
            return 1
            ;;
        401)
            echo "Error: Invalid API key. Check your BINLOOKUP_API_KEY." >&2
            return 1
            ;;
        402)
            echo "Error: No active subscription. Please subscribe to a plan." >&2
            return 1
            ;;
        403)
            echo "Error: API key lacks required permissions." >&2
            return 1
            ;;
        404)
            echo "Error: BIN $bin_number not found in database." >&2
            return 1
            ;;
        429)
            echo "Error: Daily quota exceeded. Resets at midnight UTC." >&2
            return 1
            ;;
        502|503|504)
            echo "Error: API service temporarily unavailable (HTTP $http_code). Try again later." >&2
            return 1
            ;;
        *)
            echo "Error: Unexpected HTTP status $http_code" >&2
            echo "$body" >&2
            return 1
            ;;
    esac
}

# Example usage (uncomment to test):
# lookup_bin 42467101 | jq .

Save this to a file like bin_lookup.sh and source it:

source bin_lookup.sh

# Now you can use the function
lookup_bin 42467101 | jq .

Step 5: Parsing Responses with jq

jq is essential for extracting specific fields from the API response. Here are common patterns:

# Get the full data object
lookup_bin 42467101 | jq '.data'

# Get specific fields
lookup_bin 42467101 | jq '.data.scheme'           # "visa"
lookup_bin 42467101 | jq '.data.funding'          # "debit"
lookup_bin 42467101 | jq '.data.country.name'     # "POLAND"
lookup_bin 42467101 | jq '.data.issuer.name'      # "ING BANK SLASKI SA"
lookup_bin 42467101 | jq '.data.prepaid'          # false

# Get multiple fields as a new object
lookup_bin 42467101 | jq '{
  bin: .data.bin,
  network: .data.scheme,
  type: .data.funding,
  country: .data.country.name,
  issuer: .data.issuer.name,
  prepaid: .data.prepaid
}'

# Get raw values without quotes (useful for shell variables)
scheme=$(lookup_bin 42467101 | jq -r '.data.scheme')
echo "Card network: $scheme"

# Check boolean fields
is_prepaid=$(lookup_bin 42467101 | jq -r '.data.prepaid')
if [[ "$is_prepaid" == "true" ]]; then
    echo "This is a prepaid card"
fi
* jq -r Flag

Use jq -r (raw output) when you need to use the value in shell scripts. It removes the surrounding quotes from strings.

Step 6: Batch Processing Multiple BINs

For processing multiple BINs, here’s an efficient batch script:

#!/usr/bin/env bash

# Batch BIN lookup script
# Usage: ./batch_lookup.sh bins.txt

source bin_lookup.sh  # Include the lookup_bin function

process_bins() {
    local input_file="$1"
    local output_format="${2:-json}"  # json or csv
    local success_count=0
    local error_count=0

    if [[ ! -f "$input_file" ]]; then
        echo "Error: Input file '$input_file' not found" >&2
        return 1
    fi

    # Print CSV header if needed
    if [[ "$output_format" == "csv" ]]; then
        echo "bin,scheme,funding,country,issuer,prepaid,commercial"
    fi

    while IFS= read -r bin || [[ -n "$bin" ]]; do
        # Skip empty lines and comments
        [[ -z "$bin" || "$bin" =~ ^# ]] && continue

        # Clean the BIN (remove spaces, dashes)
        bin=$(echo "$bin" | tr -d ' -')

        # Rate limiting: 100ms delay between requests
        sleep 0.1

        response=$(lookup_bin "$bin" 2>/dev/null)

        if [[ $? -eq 0 ]]; then
            ((success_count++))

            if [[ "$output_format" == "csv" ]]; then
                # Output as CSV
                echo "$response" | jq -r '[
                    .data.bin,
                    .data.scheme,
                    .data.funding,
                    .data.country.code,
                    .data.issuer.name // "N/A",
                    .data.prepaid,
                    .data.commercial
                ] | @csv'
            else
                # Output as JSON (one object per line)
                echo "$response" | jq -c '.data'
            fi
        else
            ((error_count++))
            echo "# Error looking up BIN: $bin" >&2
        fi
    done < "$input_file"

    echo "# Processed: $success_count successful, $error_count errors" >&2
}

# Run if executed directly (not sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    if [[ $# -lt 1 ]]; then
        echo "Usage: $0 <input_file> [json|csv]" >&2
        exit 1
    fi
    process_bins "$1" "${2:-json}"
fi

Create a file with BINs to process:

# bins.txt - one BIN per line
42467101
51234567
37123456
# This is a comment
60110012

Run the batch processor:

# Output as JSON lines
./batch_lookup.sh bins.txt > results.jsonl

# Output as CSV
./batch_lookup.sh bins.txt csv > results.csv
! Rate Limiting

The script includes a 100ms delay between requests to avoid hitting rate limits. Adjust this based on your plan’s rate limits.

Step 7: Real-World Example — Payment Validation Script

Here’s a practical script for validating cards in a payment processing workflow:

#!/usr/bin/env bash

# Payment Card Validation Script
# Validates a card BIN against business rules

set -euo pipefail

BINLOOKUP_API_URL="https://api.binlookupapi.com/v1/bin"

# Configuration (customize these)
BLOCK_PREPAID="${BLOCK_PREPAID:-false}"
ALLOWED_COUNTRIES="${ALLOWED_COUNTRIES:-}"  # e.g., "US,GB,CA,AU"
BLOCKED_COUNTRIES="${BLOCKED_COUNTRIES:-}"  # e.g., "RU,IR,KP"

validate_card() {
    local card_number="$1"
    local result
    local http_code
    local body

    # Extract first 8 digits (BIN)
    local bin=$(echo "$card_number" | tr -dc '0-9' | cut -c1-8)

    if [[ ${#bin} -lt 6 ]]; then
        echo '{"valid": false, "error": "Card number must be at least 6 digits"}'
        return 1
    fi

    # Make API request
    result=$(curl -s -w "\n%{http_code}" -X POST "$BINLOOKUP_API_URL" \
        -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
        -H "Content-Type: application/json" \
        -d "{\"number\": $bin}" \
        --connect-timeout 10 \
        --max-time 30 2>/dev/null) || true

    http_code=$(echo "$result" | tail -n1)
    body=$(echo "$result" | sed '$d')

    # Handle errors
    case "$http_code" in
        200)
            # Success - continue with validation
            ;;
        404)
            echo '{"valid": false, "error": "Unable to identify card. Please check the number."}'
            return 1
            ;;
        429)
            # Fail open on quota exceeded
            echo '{"valid": true, "warning": "BIN validation skipped due to quota limits"}'
            return 0
            ;;
        *)
            # Fail open on service errors
            echo "{\"valid\": true, \"warning\": \"BIN validation unavailable (HTTP $http_code)\"}"
            return 0
            ;;
    esac

    # Parse response
    local scheme=$(echo "$body" | jq -r '.data.scheme')
    local funding=$(echo "$body" | jq -r '.data.funding')
    local country_code=$(echo "$body" | jq -r '.data.country.code')
    local country_name=$(echo "$body" | jq -r '.data.country.name')
    local issuer=$(echo "$body" | jq -r '.data.issuer.name // "Unknown"')
    local is_prepaid=$(echo "$body" | jq -r '.data.prepaid')
    local is_commercial=$(echo "$body" | jq -r '.data.commercial')

    local warnings=()

    # Check prepaid restriction
    if [[ "$BLOCK_PREPAID" == "true" && "$is_prepaid" == "true" ]]; then
        jq -n \
            --arg scheme "$scheme" \
            --arg issuer "$issuer" \
            --arg country "$country_code" \
            '{
                valid: false,
                card_type: $scheme,
                issuer: $issuer,
                country: $country,
                is_prepaid: true,
                error: "Prepaid cards are not accepted"
            }'
        return 1
    fi

    # Check country restrictions
    if [[ -n "$ALLOWED_COUNTRIES" ]]; then
        if [[ ! ",$ALLOWED_COUNTRIES," =~ ",$country_code," ]]; then
            jq -n \
                --arg scheme "$scheme" \
                --arg issuer "$issuer" \
                --arg country "$country_code" \
                --arg country_name "$country_name" \
                '{
                    valid: false,
                    card_type: $scheme,
                    issuer: $issuer,
                    country: $country,
                    error: ("Cards from " + $country_name + " are not accepted")
                }'
            return 1
        fi
    fi

    if [[ -n "$BLOCKED_COUNTRIES" ]]; then
        if [[ ",$BLOCKED_COUNTRIES," =~ ",$country_code," ]]; then
            jq -n \
                --arg scheme "$scheme" \
                --arg issuer "$issuer" \
                --arg country "$country_code" \
                --arg country_name "$country_name" \
                '{
                    valid: false,
                    card_type: $scheme,
                    issuer: $issuer,
                    country: $country,
                    error: ("Cards from " + $country_name + " are not accepted")
                }'
            return 1
        fi
    fi

    # Build warnings array
    local warnings_json="[]"
    if [[ "$is_prepaid" == "true" ]]; then
        warnings_json=$(echo "$warnings_json" | jq '. + ["This is a prepaid card"]')
    fi
    if [[ "$is_commercial" == "true" ]]; then
        warnings_json=$(echo "$warnings_json" | jq '. + ["This is a corporate/business card"]')
    fi

    # Return success result
    jq -n \
        --arg scheme "$scheme" \
        --arg funding "$funding" \
        --arg issuer "$issuer" \
        --arg country "$country_code" \
        --argjson prepaid "$is_prepaid" \
        --argjson commercial "$is_commercial" \
        --argjson warnings "$warnings_json" \
        '{
            valid: true,
            card_type: $scheme,
            funding: $funding,
            issuer: $issuer,
            country: $country,
            is_prepaid: $prepaid,
            is_commercial: $commercial,
            warnings: (if ($warnings | length) > 0 then $warnings else null end)
        }'

    return 0
}

# Main execution
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    if [[ $# -lt 1 ]]; then
        echo "Usage: $0 <card_number>" >&2
        echo "" >&2
        echo "Environment variables:" >&2
        echo "  BINLOOKUP_API_KEY    - Required. Your API key" >&2
        echo "  BLOCK_PREPAID        - Block prepaid cards (true/false)" >&2
        echo "  ALLOWED_COUNTRIES    - Comma-separated country codes to allow" >&2
        echo "  BLOCKED_COUNTRIES    - Comma-separated country codes to block" >&2
        exit 1
    fi

    validate_card "$1"
fi

Example usage:

# Basic validation
./validate_card.sh 4246710012345678

# Block prepaid cards
BLOCK_PREPAID=true ./validate_card.sh 4246710012345678

# Only allow specific countries
ALLOWED_COUNTRIES="US,GB,CA,AU" ./validate_card.sh 4246710012345678

# Block specific countries
BLOCKED_COUNTRIES="RU,IR,KP" ./validate_card.sh 4246710012345678
i Fail Open Strategy

For payment validation, the script “fails 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.

Alternative: Using HTTPie

If you prefer a more user-friendly HTTP client, HTTPie is an excellent alternative to cURL:

# Install HTTPie
pip install httpie

# Basic request
http POST https://api.binlookupapi.com/v1/bin \
    Authorization:"Bearer $BINLOOKUP_API_KEY" \
    number:=42467101

# HTTPie automatically formats JSON output
# Use --print=b to print only the body
http --print=b POST https://api.binlookupapi.com/v1/bin \
    Authorization:"Bearer $BINLOOKUP_API_KEY" \
    number:=42467101

# Check response headers (quota info)
http --print=h POST https://api.binlookupapi.com/v1/bin \
    Authorization:"Bearer $BINLOOKUP_API_KEY" \
    number:=42467101
* HTTPie Syntax

Note the := syntax for numbers in HTTPie. This sends the value as a JSON number rather than a string.

Checking API Quota

Monitor your API quota using the response headers:

# Get quota information from response headers
curl -s -D - -X POST https://api.binlookupapi.com/v1/bin \
    -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"number": 42467101}' \
    -o /dev/null | grep -i "x-quota"

# Output:
# X-Quota-Limit: 15000
# X-Quota-Remaining: 14999
# X-Quota-Reset: 1706918400

A helper function to check quota:

check_quota() {
    local headers
    headers=$(curl -s -D - -X POST "$BINLOOKUP_API_URL" \
        -H "Authorization: Bearer $BINLOOKUP_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"number": 42467101}' \
        -o /dev/null)

    local limit=$(echo "$headers" | grep -i "x-quota-limit" | cut -d' ' -f2 | tr -d '\r')
    local remaining=$(echo "$headers" | grep -i "x-quota-remaining" | cut -d' ' -f2 | tr -d '\r')
    local reset=$(echo "$headers" | grep -i "x-quota-reset" | cut -d' ' -f2 | tr -d '\r')
    local reset_date=$(date -r "$reset" 2>/dev/null || date -d "@$reset" 2>/dev/null || echo "N/A")

    echo "Quota Limit:     $limit"
    echo "Quota Remaining: $remaining"
    echo "Quota Resets:    $reset_date"
}

Best Practices Summary

Production Checklist
  • Store API key in BINLOOKUP_API_KEY environment variable
  • Use the -s (silent) flag to suppress cURL progress output
  • Always capture and check HTTP status codes
  • Handle all error types: 400, 401, 402, 403, 404, 429, 5xx
  • Set connection and request timeouts (--connect-timeout, --max-time)
  • Use jq for reliable JSON parsing (never parse with grep/sed)
  • Implement rate limiting in batch scripts (sleep between requests)
  • Consider fail-open strategy for non-critical validation
  • Monitor your quota usage via X-Quota-* response headers
  • Quote variables properly to handle special characters

Next Steps

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