Skip to content

Rate Limits ​

Understanding and working with API rate limits to ensure reliable service.

Overview ​

The MsGine API rate limits per API key, so keys in the same account have independent budgets.

The limit ​

100 requests per minute, per API key.

The window slides: each request is counted for 60 seconds from when it was made, and capacity returns as individual requests age out. 100 requests in one second is within the limit; the 101st within the same 60 seconds returns 429.

Rate Limit Headers ​

Every API response includes rate limit information in headers:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
HeaderDescription
X-RateLimit-LimitTotal requests allowed per minute
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when limit resets

Rate Limit Exceeded ​

When you exceed the rate limit, you'll receive a 429 Too Many Requests response:

json
{
  "statusCode": 429,
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded"
}

Read the retry delay from the Retry-After header rather than the body.

The Retry-After header indicates when you can retry (in seconds):

http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000060

Retry-After is the seconds until the oldest counted request leaves the window, so it is usually well under a minute — not a flat 60.

Handling Rate Limits ​

Manual Retry Logic ​

If using the REST API directly, implement retry logic:

typescript
async function sendWithRetry(url: string, data: any, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'X-Api-Key': process.env.MSGINE_API_KEY!,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    })

    if (response.status !== 429) {
      return response.json()
    }

    const retryAfter = parseInt(response.headers.get('Retry-After') || '60')
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
  }

  throw new Error('Max retries exceeded')
}

Check Rate Limit Status ​

Monitor your rate limit status:

typescript
async function checkRateLimit() {
  const response = await fetch('https://api.msgine.net/api/v1/account', {
    headers: {
      'X-Api-Key': process.env.MSGINE_API_KEY!,
    }
  })

  const limit = response.headers.get('X-RateLimit-Limit')
  const remaining = response.headers.get('X-RateLimit-Remaining')
  const reset = response.headers.get('X-RateLimit-Reset')

  console.log(`Rate Limit: ${remaining}/${limit}`)
  console.log(`Resets at: ${new Date(parseInt(reset!) * 1000)}`)
}

Best Practices ​

1. Implement Exponential Backoff ​

typescript
async function exponentialBackoff(attempt: number) {
  const delay = Math.min(1000 * Math.pow(2, attempt), 10000)
  await new Promise(resolve => setTimeout(resolve, delay))
}

2. Use Request Queuing ​

For high-volume applications, implement a request queue:

typescript
class RequestQueue {
  private queue: Array<() => Promise<any>> = []
  private processing = false
  private readonly requestsPerMinute = 100

  async enqueue<T>(request: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await request()
          resolve(result)
        } catch (error) {
          reject(error)
        }
      })

      if (!this.processing) {
        this.process()
      }
    })
  }

  private async process() {
    this.processing = true
    const interval = 60000 / this.requestsPerMinute

    while (this.queue.length > 0) {
      const request = this.queue.shift()!
      await request()
      await new Promise(resolve => setTimeout(resolve, interval))
    }

    this.processing = false
  }
}

3. Batch Recipients ​

Send to multiple recipients in a single request instead of multiple requests:

typescript
// ✅ Better - Single request for multiple recipients
await client.sms.send({
  to: phones,
  message: 'Hello!'
})

// ❌ Not optimal - Multiple requests
for (const phone of phones) {
  await client.sms.send({ to: phone, message: 'Hello!' })
}

4. Cache Responses ​

Cache responses when appropriate:

typescript
const cache = new Map()

async function getAccountInfo() {
  const cached = cache.get('account')
  if (cached && Date.now() - cached.timestamp < 60000) {
    return cached.data
  }

  const data = await client.getAccount()
  cache.set('account', {
    data,
    timestamp: Date.now()
  })

  return data
}

5. Monitor Usage ​

Track your rate limit usage:

typescript
function checkRateLimitHealth(remaining: number, limit: number) {
  const percentage = (remaining / limit) * 100

  if (percentage < 10) {
    console.error('Critical: Less than 10% rate limit remaining')
  } else if (percentage < 25) {
    console.warn('Warning: Less than 25% rate limit remaining')
  }
}

Increasing Rate Limits ​

To increase your rate limits:

  1. Upgrade your plan: Higher tiers have higher limits
  2. Contact sales: Enterprise customers can request custom limits
  3. Optimize usage: Implement batching and caching

Rate Limit by Endpoint ​

Different endpoints may have different rate limits:

EndpointLimit (req/min)
POST /developers/smsPlan-dependent
GET /developers/sms/historyPlan-dependent
GET /accountPlan-dependent
POST /webhooks20

Concurrency ​

Requests are counted individually, not by connection. Parallel requests consume the same budget as sequential ones.

Next Steps ​

Released under the MIT License.