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.