emcognito
Back to Blog

Handling API Burst Limits: Architecting Your App Around Emcognito's Alias API

November 14, 2026

Updated

developer apirate limitingemail aliasesapi integration429 errorsemcognito plusemcognito pro

Keep your real inbox private.

Unlimited aliases, free. No credit card, passwordless sign-in.

Create a free alias →

Robust email alias api burst limit handling requires separating network-level rate throttling from application-level quota exhaustion. Two boundaries exist. If your client treats every 429 response identically, your service will either hammer a locked gateway or sleep for hours when it only needed to pause for sixty seconds. Emcognito enforces a 60-call-per-minute burst limit per key with a Retry-After header, alongside an application-level daily alias creation quota that carries no header and resets strictly at 00:00 UTC.

When you build against Emcognito's developer API, your code interacts with these distinct limits. The burst throttle restricts request velocity to 60 calls per minute per API key at the routing edge. The daily creation cap meters new aliases: 50 daily on Plus or 200 daily on Pro, enforced by application logic. Both scenarios return HTTP status code 429. They fail differently. Each status conveys different recovery metadata and requires a different retry path. Reliable clients inspect these headers before retrying any write.

Two limits, two different 429s

To design resilient api rate limiting logic, branch on the failure mechanism. The burst limit and the daily alias-creation cap produce different response profiles:

  • The edge rate limiter enforces the burst threshold at 60 calls per minute per API key. When your application exceeds this threshold, the server returns HTTP 429 with a Retry-After header expressed in whole seconds. The limiter supplies the full window duration, meaning the value is conservative: sleeping for the indicated seconds ensures the client retries late rather than early. Live measurement on /v1/aliases verifies that the 61st call within a 60-second sliding period returns HTTP 429 with retry-after: 60.
  • Application code enforces the daily creation cap when an account reaches its allotment of new aliases (50 per day on Plus, 200 per day on Pro). When reached, POST requests return HTTP 429 with a bare JSON payload. This response carries no Retry-After header. The quota resets at 00:00 UTC.
  • Both 429 responses return a bare JSON object: {"message": "..."}. The payload includes no structured error code or machine-readable reason parameter. Your code must infer the cause by checking for the presence of the Retry-After header.

POST /v1/aliases returns HTTP 200 with the new alias under "alias". The burst limit is 60 requests a minute per key; its 429 carries a Retry-After header giving the seconds until the window resets. The daily alias-creation cap (50 on Plus, 200 on Pro) resets at 00:00 UTC and its 429 carries no Retry-After. Emcognito sends no X-RateLimit-* headers.

Experimental specifications like the IETF HTTPAPI RateLimit header fields draft propose standard headers for remaining calls and reset epochs, but Emcognito does not emit them. Do not write parsers looking for remaining counts or limit windows. The response is compact. Your client must evaluate the HTTP status, inspect Retry-After, and handle edge timeouts directly.

What the API actually returns

Before implementing error handling, confirm your serialization types match the API contract. Emcognito serves its REST endpoints under the base URL https://api.emcognito.com/v1 (there is no /api/v1 path). Both endpoints require authentication. All requests require a Bearer token in the Authorization header:

Authorization: Bearer emk_0123456789abcdefghijklmnopqrstuvwxyzABCDEF01

Keys use the literal prefix emk_ followed by 43 URL-safe characters. No live or test split exists; every key targets production data. The developer API surface in version 1 consists of two endpoints: GET /v1/aliases to list and paginate aliases, and POST /v1/aliases to mint a new alias.

When creating an alias, the request body accepts an optional JSON payload with the following optional keys: label (string), note (string), source (string), category (string), single_use (boolean), and expires_at (integer epoch). On success, the API returns HTTP 200, not HTTP 201 Created. The returned JSON structure nests the data under an alias key:

{
  "alias": {
    "id": "9f8d7e6c5b4a",
    "address": "x8k2p9m1q@emcognito.com",
    "status": "active",
    "created_at": 1731580800,
    "forward_count": 0,
    "label": "checkout-service",
    "note": "provisioned for user 8492",
    "source": "api-worker",
    "category": "transactions",
    "single_use": false,
    "expires_at": 0
  }
}

The format is strict. created_at is an integer Unix epoch timestamp, not an ISO-8601 string. Any optional string field omitted from your request is returned as an empty string (""), not null. The API provides no PATCH, PUT, or DELETE endpoints, and there is no /v1/aliases/{id} subresource. Pausing an alias is performed manually via the dashboard using the "Pause alias" button, and deleting an alias requires confirming a dashboard prompt.

Retry logic that reads Retry-After correctly

When building resilient email alias api burst limit handling, the core retry interceptor must implement three execution branches based on status codes and response headers:

  1. For a burst limit 429, parse Retry-After as a whole integer of seconds. The rate limiter supplies the entire window duration, so retrying early guarantees another rejection. Sleep for the specified duration plus a small random jitter between 250ms and 1000ms to avoid synchronized retries across threads.
  2. For an edge 503 response, read the fixed Retry-After: 30 header. Brief gateway recalculations or rate-limiter saturation trigger this response. Sleep for the indicated 30 seconds plus jitter before attempting the request again.
  3. For a quota 429 where Retry-After is absent, stop retries. This response indicates the daily creation limit was reached (50 aliases on Plus, 200 on Pro). Do not retry immediately. This cap resets only when the UTC clock rolls over. Calculate the seconds remaining until 00:00 UTC to reschedule the job, or fail the write operation immediately.

The following Python snippet illustrates this branching logic cleanly without arbitrary delays:

import time
import random
from datetime import datetime, timezone, timedelta
import requests

def make_emcognito_request(method, url, api_key, payload=None, max_burst_retries=3): headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }

attempts = 0
while True:
    response = requests.request(method, url, headers=headers, json=payload, timeout=10)
    
    # Success path (Emcognito POST returns 200, GET returns 200)
    if response.status_code == 200:
        return response.json()
        
    # Handle burst rate limits (429) and edge throttle errors (503)
    if response.status_code in (429, 503):
        retry_after_header = response.headers.get("Retry-After")
        
        if retry_after_header:
            attempts += 1
            if attempts > max_burst_retries:
                raise RuntimeError(f"Exceeded max burst retries ({max_burst_retries}) on {url}")
            
            # Integer seconds returned by edge limiter
            sleep_seconds = int(retry_after_header)
            jitter = random.uniform(0.25, 1.0)
            time.sleep(sleep_seconds + jitter)
            continue
            
        # Missing Retry-After on a 429 indicates daily creation cap reached
        if response.status_code == 429:
            now_utc = datetime.now(timezone.utc)
            tomorrow_utc = (now_utc + timedelta(days=1)).replace(
                hour=0, minute=0, second=0, microsecond=0
            )
            wait_seconds = (tomorrow_utc - now_utc).total_seconds()
            raise QuotaExhaustedError(
                f"Daily alias creation cap reached. Window resets in {int(wait_seconds)} seconds at 00:00 UTC."
            )

    # Unhandled client or server errors
    response.raise_for_status()

class QuotaExhaustedError(Exception): pass

Queueing alias creation to avoid burst limits

While retry logic catches overflow, high-volume systems should throttle outbound requests upstream. Upstream workers need pacing. Because the 60 request-per-minute burst limit is calculated per API key, multiple distributed workers sharing a single credential pool quickly exhaust the limit if they send simultaneous calls.

Consider the architecture of web sign-ups. Many systems provision an alias during checkout, user registration, or lead processing. According to FTC guidance on how websites and apps collect and use information, segregating customer contact channels limits unintended data profiling. However, firing synchronous HTTP requests during user-facing requests introduces latency and exposes you to edge rate drops. Instead, dispatch alias requests into a centralized message queue governed by a token-bucket rate limiter.

Configure your queue consumer to release a maximum of 55 requests per minute per key. This protects your limit. Keeping baseline dispatch rates below 60 gives your system headroom to handle manual admin calls, health checks, or pagination listings from the same key without triggering 429 responses.

Handling network timeouts and reconciliation

Emcognito does not support custom idempotency headers such as Idempotency-Key. If an HTTP POST request to /v1/aliases times out after the gateway processes the write, repeating the request mints an unintended duplicate alias that burns an extra unit of your daily creation cap.

To prevent duplicates, use the request's label or source fields as a reconciliation anchor:

  • Assign your internal transaction or entity ID to the label parameter, such as usr_9821_checkout.
  • Catch the network exception if the HTTP connection drops before receiving the 200 payload.
  • Query GET /v1/aliases before attempting a duplicate write.
  • Adopt the existing record's address if an alias exists with your designated label, dispatching a new POST only when the label is missing.

This reconciliation pattern ensures upstream workers remain idempotent even when transient network partitions disrupt connections.

Planning around the daily creation cap

The daily alias-creation cap operates separately from the burst limit. The burst limit controls request concurrency and edge load; the daily cap meters database writes for new forwarding addresses. Understanding how this cap applies to your subscription helps you budget your API consumption:

  • On the Plus plan, the API creation limit is 50 aliases per day.
  • On the Pro plan, the API creation limit is 200 aliases per day.

Listing existing aliases via GET /v1/aliases consumes your 60-per-minute burst rate, but it does not count against your daily creation quota. Inbound message forwarding has nothing to do with the alias creation cap. Accounts support unlimited aliases across every tier; what is metered on Emcognito is forwarded email volume (2,500 forwards per month on Plus; 15,000 on Pro). You can verify your forwarding balances and review plan features on the Emcognito pricing page.

Batch jobs require care. The cap resets at midnight. Because the daily counter resets at 00:00 UTC, avoid scheduling large provisioning batches around local midnight if your infrastructure operates in other timezones. Converting all batch job triggers to reference UTC boundaries avoids mid-batch quota failures.

When the burst limit is the wrong problem

If your service constantly runs into the 60 calls/minute burst barrier, your integration may have architectural inefficiencies. The two most common culprits are polling loops and unified worker pools.

Polling for changes

Emcognito does not emit webhooks when an alias receives an email or when its status updates. Some developers attempt to detect activity by looping through GET /v1/aliases across pagination keys. Polling this endpoint once every few seconds consumes your burst allowance, leaving no capacity for POST requests when real users register.

To avoid this, cache your alias states locally in a database. Only query the listing endpoint on low-frequency background schedules or when performing manual reconciliations. As documented in Pew Research Center research on email use, message transactions remain continuous throughout business operations; your application should handle message reception asynchronously rather than polling an alias management API.

Separating read and write keys

Because the 60/minute limit applies to the individual API token, sharing a single key across independent microservices creates resource contention. A bulk export job scanning historical aliases can exhaust the key's burst window, blocking user signups in another service. Segment your workloads or regulate reads and writes through distinct queues so administrative listings do not block transactional alias creation.

Where Emcognito's API stops

Architecting an integration requires recognizing what the API intentionally does not do. Emcognito maintains a streamlined endpoint footprint designed specifically for alias generation and tracking. Factoring these boundaries into your design prevents unnecessary engineering:

  • The v1 API does not expose endpoints to pause, resume, or delete aliases. Suspending an alias is performed with a single click using the "Pause alias" button in the dashboard, and deletion requires answering a confirmation prompt. If your system requires automated deactivation rules, plan around dashboard management. Pausing suspicious addresses manually remains effective when inspecting sender behaviour; for instance, the FTC phishing guidance advises immediately isolating unverified sender channels.
  • Emcognito aliases use the shared emcognito.com domain. Custom domains are not available today; a custom subdomain at handle.emcognito.com is available on Pro.
  • Emcognito is closed source and runs only as a hosted service; if open source or self-hosting is a requirement, addy.io is the better fit. You can see how these architectures contrast on our addy.io comparison guide.
  • The Free plan is only for accounts opened before October 29, 2026. New accounts choose between Plus and Pro. Pro includes a 7-day free trial requiring a credit card at checkout with no charge during the trial period. Plus does not offer a trial; it is billed at checkout, and the first payment is refundable within 14 days upon request from the Billing dashboard.

A reference client, end to end

The following Python class combines edge throttle handling, 503 handling, daily creation quota detection, and reconciliation lookups into a unified client. It maintains state defensively, ensuring your application avoids dangling requests or stalled execution loops:

import time
import random
from datetime import datetime, timezone, timedelta
from typing import Optional, Dict, Any
import requests

class EmcognitoClient: BASE_URL = "https://api.emcognito.com/v1"

def __init__(self, api_key: str):
    if not api_key.startswith("emk_") or len(api_key) != 47:
        raise ValueError("Invalid key format. Expected 'emk_' prefix followed by 43 characters.")
    self.api_key = api_key
    self.session = requests.Session()
    self.session.headers.update({
        "Authorization": f"Bearer {self.api_key}",
        "Content-Type": "application/json"
    })

def _execute(self, method: str, path: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    url = f"{self.BASE_URL}{path}"
    max_burst_attempts = 4
    attempts = 0

    while True:
        try:
            resp = self.session.request(method, url, json=payload, timeout=12)
        except requests.RequestException as e:
            # Network transport issue; upstream should reconcile before retrying writes
            raise ConnectionError(f"Transport failure connecting to {url}: {e}")

        if resp.status_code == 200:
            return resp.json()

        # Handle 429 Too Many Requests and 503 Edge Throttles
        if resp.status_code in (429, 503):
            retry_header = resp.headers.get("Retry-After")

            if retry_header is not None:
                attempts += 1
                if attempts > max_burst_attempts:
                    raise RuntimeError(f"Exceeded {max_burst_attempts} burst retries on {path}")

                # Respect edge delay: whole seconds plus jitter
                sleep_duration = int(retry_header) + random.uniform(0.1, 0.75)
                time.sleep(sleep_duration)
                continue

            # 429 without Retry-After signifies the daily application limit
            if resp.status_code == 429:
                now = datetime.now(timezone.utc)
                reset_time = (now + timedelta(days=1)).replace(
                    hour=0, minute=0, second=0, microsecond=0
                )
                secs_to_reset = int((reset_time - now).total_seconds())
                raise DailyCapExhausted(
                    f"Daily alias creation limit reached. Quota resets at 00:00 UTC (in {secs_to_reset}s)."
                )

        # Unhandled errors (e.g., 400 Bad Request, 401 Unauthorized)
        resp.raise_for_status()

def create_alias_safe(self, label: str, source: str = "api") -> Dict[str, Any]:
    """
    Creates an alias with pre-flight reconciliation to prevent duplicate creation on retry.
    """
    existing = self.find_alias_by_label(label)
    if existing:
        return existing

    body = {
        "label": label,
        "source": source
    }
    res = self._execute("POST", "/aliases", payload=body)
    return res["alias"]

def find_alias_by_label(self, label: str) -> Optional[Dict[str, Any]]:
    """
    Scans recent aliases to check if an operation already minted the target address.
    """
    data = self._execute("GET", "/aliases")
    aliases = data.get("aliases", [])
    for item in aliases:
        if item.get("label") == label:
            return item
    return None

class DailyCapExhausted(Exception): pass

When implementing your client, keep your API key confined strictly to server-side workers. Do not expose Bearer tokens inside client-side applications, frontend web bundles, or mobile applications.

Frequently asked questions

Does Emcognito's API return rate-limit headers I can read?

Only Retry-After is provided, and it appears only on the 60/minute burst 429 response and the rate-limiting 503 response. It is not returned on successful 200 responses or on the daily creation quota 429. Emcognito sends no other rate-limit headers, so clients should not look for remaining-quota headers.

What HTTP status does POST /v1/aliases return on success?

The endpoint returns HTTP 200 (not 201 Created). The response body encloses the created record within an alias object containing fields such as id, address, status, and an integer Unix epoch created_at timestamp.

Can I suspend or delete an alias through the developer API?

No. Check the dashboard instead. Version 1 of the API exposes only GET /v1/aliases and POST /v1/aliases. Pausing an alias is performed manually via the "Pause alias" button in the dashboard, and deleting an alias requires confirming the deletion prompt inside the dashboard.

How long should I wait after a daily alias-creation 429?

You must wait until the next rollover at 00:00 UTC. The daily creation limit (50 on Plus, 200 on Pro) is reset daily at midnight UTC. Because this 429 originates from application code rather than the network rate limiter, it carries no Retry-After header.

Is the 60-per-minute burst limit per account or per API key?

The limit applies per API key. Each key has an independent allocation of 60 requests per minute across both read and write operations. If you run multiple decoupled systems, ensure total calls across all threads sharing that key do not exceed 60 requests per minute.

To start provisioning addresses from code or inspect the complete parameter schema, review the developer alias API documentation.

Sources and further reading

Make Emcognito a preferred source on Google

Create aliases from your own code.

Endpoints to create, list, update and delete aliases, Bearer auth, 50 new aliases a day on Plus and 200 on Pro. Signed webhooks on Pro.

Read the API docs →