Skip to main content

Overview

The Owem API implements rate limiting to ensure stability and service quality for all customers.
Limits are applied per API Key and per endpoint.

Default Limits

TypeLimitPeriod
Global1000 req1 minute
Per Endpoint100 req1 minute
PIX Transfers60 req1 minute
QR Code Generation120 req1 minute
Limits may vary according to your plan. Contact support to increase limits.

Rate Limit Headers

The API returns informative headers in each response:
HeaderDescriptionExample
X-RateLimit-LimitTotal limit for the period1000
X-RateLimit-RemainingRemaining requests847
X-RateLimit-ResetReset timestamp (epoch)1766610300

Response Example

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1766610300
Content-Type: application/json

{"success": true, ...}

When Limit is Exceeded

When exceeding the rate limit, you’ll receive:
{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": 429,
  "success": false,
  "message": "Too many requests",
  "code": "RATE_LIMIT_EXCEEDED"
}

Additional Headers on 429

HeaderDescription
Retry-AfterSeconds to wait before retry

Implementing Traffic Control

Exponential Backoff

Implement retry with exponential backoff for 429 errors:
async function callWithRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s...
        console.log(`Rate limited. Retry in ${delay}ms`)
        await sleep(delay)
        continue
      }
      throw error
    }
  }
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

Token Bucket (Client Side)

Control your request rate on your side:
class RateLimiter {
  constructor(maxTokens, refillRate) {
    this.maxTokens = maxTokens;
    this.tokens = maxTokens;
    this.refillRate = refillRate; // tokens per second
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refill();

    if (this.tokens < 1) {
      const waitTime = (1 - this.tokens) / this.refillRate * 1000;
      await sleep(waitTime);
      this.refill();
    }

    this.tokens -= 1;
    return true;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
    this.lastRefill = now;
  }
}

// Usage: 100 req/min = ~1.67 req/s
const limiter = new RateLimiter(10, 1.67);

async function callOwemAPI(endpoint) {
  await limiter.acquire();
  return fetch(`https://api.owem.com.br${endpoint}`, { ... });
}

Request Queue

For high volume, use a queue:
import PQueue from "p-queue"

// 10 simultaneous requests, 100ms between each
const queue = new PQueue({ concurrency: 10, interval: 100, intervalCap: 1 })

async function queuedRequest(endpoint, options) {
  return queue.add(() => fetch(`https://api.owem.com.br${endpoint}`, options))
}

// Usage
const results = await Promise.all([
  queuedRequest("/v4/i/ledger?page=1"),
  queuedRequest("/v4/i/ledger?page=2"),
  queuedRequest("/v4/i/ledger?page=3"),
  // ... hundreds of requests will be queued
])

Best Practices

Monitor Headers

Track X-RateLimit-Remaining to anticipate throttling.

Cache Queries

Cache frequent queries (balance, ledger) to reduce requests.

Batch When Possible

Use optimized pagination instead of multiple small requests.

Webhooks > Polling

Use webhooks for events instead of constant polling.

Complete Example

class OwemClient {
  constructor(apiKey, apiSecret) {
    this.baseUrl = "https://api.owem.com.br"
    this.token = Buffer.from(`${apiKey}:${apiSecret}`).toString("base64")
    this.limiter = new RateLimiter(100, 1.67)
  }

  async request(method, endpoint, body = null) {
    await this.limiter.acquire()

    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method,
      headers: {
        Authorization: `Basic ${this.token}`,
        "Content-Type": "application/json",
      },
      body: body ? JSON.stringify(body) : null,
    })

    // Update limiter with real data
    const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"))
    const reset = parseInt(response.headers.get("X-RateLimit-Reset"))

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get("Retry-After")) || 60
      console.warn(`Rate limited. Waiting ${retryAfter}s`)
      await sleep(retryAfter * 1000)
      return this.request(method, endpoint, body)
    }

    return response.json()
  }

  // Convenience methods
  getBalance(accountId) {
    return this.request("GET", `/v4/i/bank-accounts/${accountId}/balance`)
  }

  createQrCode(data) {
    return this.request("POST", "/v4/i/pix/in/dynamic-qrcode", data)
  }
}

Support

If you need higher limits for your use case, get in touch: