npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

swarmkey

v1.0.3

Published

API Key Swarm Manager - Automatically rotate multiple API keys to handle rate limits

Readme

🔑 SwarmKey

Simple API Key Manager - Automatically rotate through multiple API keys with cooldown management.

License: MIT

🎯 What is SwarmKey?

SwarmKey is a lightweight, framework-agnostic API key manager that helps you:

  • Manage multiple API keys in a single pool
  • Automatic cooldown when keys hit rate limits (429 errors)
  • Simple rotation to the next available key
  • Zero dependencies on specific AI providers
  • Works with any API (Gemini, OpenAI, Claude, Anthropic, etc.)

Perfect for: Applications that need to distribute API usage across multiple keys with automatic failover.


🚀 Quick Start

Installation

npm install swarmkey

Basic Usage

import SwarmKey from 'swarmkey'

// Initialize with your API keys
const sk = SwarmKey.init({
  keys: ['key1', 'key2', 'key3']
})

// Get an available key
const key = sk.getAvailableKey()

if (key) {
  try {
    // Use the key with YOUR API client
    const response = await yourApiClient.call(key, 'Your prompt here')

  } catch (error) {
    // If you get 429 rate limit error, mark the key as failed
    if (error.status === 429) {
      sk.markKeyFailed(key)  // Key will be in cooldown for 50 minutes
    }
  }
} else {
  console.log('No keys available right now')
}

📖 Usage Examples

With Environment Variables

# .env
SWARM_KEYS="key1,key2,key3,key4,key5"
import SwarmKey from 'swarmkey'
import 'dotenv/config'

// Initialize from environment variables
const sk = SwarmKey.init()

const key = sk.getAvailableKey()
// Use the key...

Error Handling with Automatic Cooldown

import SwarmKey from 'swarmkey'

const sk = SwarmKey.init({
  keys: ['key1', 'key2', 'key3']
})

async function makeApiCall(prompt: string) {
  const key = sk.getAvailableKey()

  if (!key) {
    throw new Error('No keys available')
  }

  try {
    // Make your API call
    const response = await yourApiClient.call(key, prompt)
    return response

  } catch (error) {
    // Handle rate limit error
    if (error.status === 429) {
      // Mark key as failed - it will be unavailable for 50 minutes
      sk.markKeyFailed(key)

      // Try with next available key
      return makeApiCall(prompt)
    }

    throw error
  }
}

Get All Available Keys

const sk = SwarmKey.init({
  keys: ['key1', 'key2', 'key3']
})

// Get all keys that are currently available (not in cooldown)
const availableKeys = sk.getAvailableKeys()

console.log(`${availableKeys.length} keys available`)

// Use them for parallel requests
const promises = availableKeys.map(async (key) => {
  try {
    return await yourApiClient.call(key)
  } catch (error) {
    if (error.status === 429) {
      sk.markKeyFailed(key)
    }
    throw error
  }
})

const results = await Promise.allSettled(promises)

Monitor Keys Status

const sk = SwarmKey.init({
  keys: ['key1', 'key2', 'key3']
})

// Get detailed status for all keys
const status = sk.getKeysStatus()

status.forEach(keyInfo => {
  console.log(`Key: ${keyInfo.keyPreview}`)
  console.log(`  Available: ${keyInfo.available}`)
  console.log(`  In cooldown: ${keyInfo.inCooldown}`)

  if (keyInfo.cooldownRemainingMs) {
    const minutes = Math.ceil(keyInfo.cooldownRemainingMs / 60000)
    console.log(`  Cooldown remaining: ${minutes} minutes`)
  }
})

// Get counts
console.log(`Total keys: ${sk.getTotalKeys()}`)
console.log(`Available: ${sk.getAvailableKeysCount()}`)

⚙️ Configuration

Environment Variables

| Variable | Description | Required | Default | |----------|-------------|----------|---------| | SWARM_KEYS | Comma-separated API keys | Yes* | - |

*Can be provided via config object instead

Initialization Options

interface SwarmKeyConfig {
  keys?: string[]  // API keys to manage
}

📊 How It Works

Key Selection Logic

Request for key
      │
      ▼
┌─────────────────┐
│ Get all keys    │
└────────┬────────┘
         │
         ▼
┌─────────────────────────┐
│ For each key:           │
│ 1. Check if in cooldown │
│ 2. Clear expired        │
│    cooldowns            │
└────────┬────────────────┘
         │
         ▼
┌─────────────────┐     ┌──────────────┐
│ Key available?  │────▶│ Return key   │
└─────────┬───────┘     └──────────────┘
          │
          ▼ No
┌─────────────────┐
│ Try next key    │
└─────────────────┘

Cooldown Management

  • Simple State: Each key is either available or in cooldown
  • Automatic Recovery: Keys automatically become available after 50 minutes
  • 429 Handling: When a key hits rate limit (429), mark it as failed with markKeyFailed(key)
  • No Tracking: No request counters or rate limit tracking needed

🧮 API Reference

SwarmKey.init(config?)

Initialize a new SwarmKey instance.

static init(config?: SwarmKeyConfig): SwarmKey

Parameters:

  • config.keys?: string[] - Array of API keys (or use SWARM_KEYS env var)

Returns: SwarmKey instance

Throws: Error if no keys provided


getAvailableKey()

Get an available API key (not in cooldown).

getAvailableKey(): string | null

Returns: An available key, or null if all keys are in cooldown


getAvailableKeys()

Get all available API keys (not in cooldown).

getAvailableKeys(): string[]

Returns: Array of available keys (may be empty)


markKeyFailed(key)

Mark a key as failed, putting it in cooldown for 50 minutes.

markKeyFailed(key: string): void

Parameters:

  • key - The API key that failed (received 429 error)

Use this when a key returns a 429 rate limit error. The key will be unavailable for 50 minutes.


getKeysStatus()

Get detailed status for all keys.

getKeysStatus(): Array<{
  key: string
  keyPreview: string
  available: boolean
  inCooldown: boolean
  cooldownUntil: number | null
  cooldownRemainingMs: number | null
}>

getTotalKeys()

Get total number of keys being managed.

getTotalKeys(): number

getAvailableKeysCount()

Get count of keys currently available (not in cooldown).

getAvailableKeysCount(): number

🛠️ Integration Examples

With Google Gemini API

import { GoogleGenerativeAI } from '@google/generative-ai'
import SwarmKey from 'swarmkey'

const sk = SwarmKey.init({
  keys: ['gemini-key-1', 'gemini-key-2', 'gemini-key-3']
})

async function generate(prompt: string) {
  const key = sk.getAvailableKey()

  if (!key) {
    throw new Error('No keys available')
  }

  try {
    const genAI = new GoogleGenerativeAI(key)
    const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' })
    const result = await model.generateContent(prompt)

    return result.response.text()

  } catch (error) {
    if (error.status === 429) {
      sk.markKeyFailed(key)
      // Retry with next key
      return generate(prompt)
    }
    throw error
  }
}

With OpenAI API

import OpenAI from 'openai'
import SwarmKey from 'swarmkey'

const sk = SwarmKey.init({
  keys: ['openai-key-1', 'openai-key-2']
})

async function chat(message: string) {
  const key = sk.getAvailableKey()

  if (!key) {
    throw new Error('No keys available')
  }

  try {
    const openai = new OpenAI({ apiKey: key })
    const completion = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: message }]
    })

    return completion.choices[0].message.content

  } catch (error) {
    if (error.status === 429) {
      sk.markKeyFailed(key)
      // Retry with next key
      return chat(message)
    }
    throw error
  }
}

With Anthropic Claude API

import Anthropic from '@anthropic-ai/sdk'
import SwarmKey from 'swarmkey'

const sk = SwarmKey.init({
  keys: ['anthropic-key-1', 'anthropic-key-2']
})

async function askClaude(prompt: string) {
  const key = sk.getAvailableKey()

  if (!key) {
    throw new Error('No keys available')
  }

  try {
    const anthropic = new Anthropic({ apiKey: key })
    const message = await anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 1024,
      messages: [{ role: 'user', content: prompt }]
    })

    return message.content[0].text

  } catch (error) {
    if (error.status === 429) {
      sk.markKeyFailed(key)
      // Retry with next key
      return askClaude(prompt)
    }
    throw error
  }
}

🧪 Testing

Run Tests

npm test

Build

npm run build

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

# Clone repository
git clone https://github.com/yourusername/swarmkey.git
cd swarmkey

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

📄 License

MIT License - see LICENSE file for details.


💡 Tips & Best Practices

1. Retry Logic with Exponential Backoff

async function callWithRetry(prompt: string, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const key = sk.getAvailableKey()

    if (!key) {
      await sleep(5000)  // Wait 5 seconds if no keys available
      continue
    }

    try {
      const result = await apiCall(key, prompt)
      return result
    } catch (error) {
      if (error.status === 429) {
        sk.markKeyFailed(key)
        continue
      }
      throw error
    }
  }

  throw new Error('All retries exhausted')
}

2. Monitor Key Health

setInterval(() => {
  const total = sk.getTotalKeys()
  const available = sk.getAvailableKeysCount()

  console.log(`Keys: ${available}/${total} available`)

  if (available < total * 0.3) {
    console.warn('WARNING: Less than 30% keys available!')
  }
}, 60000)  // Check every minute

3. Graceful Degradation

async function makeRequest(prompt: string) {
  const key = sk.getAvailableKey()

  if (!key) {
    // Fallback strategy
    console.warn('No keys available, using fallback')
    return await fallbackStrategy(prompt)
  }

  try {
    return await apiCall(key, prompt)
  } catch (error) {
    if (error.status === 429) {
      sk.markKeyFailed(key)
      return makeRequest(prompt)  // Retry
    }
    throw error
  }
}

❓ FAQ

Q: Does this work with any API provider?

A: Yes! SwarmKey is completely framework-agnostic. It just manages keys and cooldowns.

Q: Can I use this in browser/frontend?

A: Not recommended. API keys should be kept server-side for security.

Q: What happens when all keys are in cooldown?

A: getAvailableKey() returns null. Implement retry logic or wait for cooldowns to expire.

Q: Can I customize the cooldown duration?

A: Currently fixed at 50 minutes. This matches typical API provider rate limit windows.

Q: Does it track request counts or rate limits?

A: No. SwarmKey only tracks failed/available state. This keeps it simple and framework-agnostic.


Made with ❤️ for developers dealing with API rate limits