8 Min. LesezeitEngineering

URL-Shortener-API: Rate Limits, Retries, Idempotenz

Eine URL-Shortener-API produktionsreif aufrufen: Token-Bucket-Limits, Status-Codes mit Backoff-Retry und Idempotency-Keys gegen Duplikate.

Marius Voß
DevRel · edge infra

Drei Endpunkte, ein Auth-Header, ein JSON-Body. Eine URL-Shortener-API ist eine der einfachsten Integrationen auf jeder Backlog-Liste, und das Quickstart-Tutorial bringt Sie in wenigen Minuten zu einem funktionierenden Kurzlink. Was das Quickstart-Tutorial überspringt, ist alles, was passiert, wenn die Integration unter Last läuft: der Rate Limiter, der zurückdrückt; ein transientes 503 mitten im Batch; eine Job-Queue, die dieselbe Nachricht zweimal liefert. Fehler dabei erzeugen doppelte Links, verlorene Arbeit und einen 429-Sturm, der die Lage verschlimmert.

Dieser Beitrag ist der Produktionshärtungs-Begleiter zum API-Quickstart. Er behandelt die drei Mechanismen, die eine Demo von einer zuverlässigen Integration trennen: Rate Limits und wie Sie sich dagegen schrittweise verhalten, welche Fehler Sie erneut versuchen und wie Sie Backoff betreiben, sowie Idempotency-Keys, die einen Retry daran hindern, einen zweiten Link zu erstellen. Die Beispiele verwenden Elidos API, aber die Muster gelten für jede sauber aufgebaute Link-Shortener-API. Wenn Sie Kurzlinks als Infrastruktur behandeln, die Sie per Code verwalten, finden Sie den weiteren Kontext dazu in Kurzlinks als Terraform.

Rate Limits: ein Token-Bucket und ein Retry-After-Header

Elido dosiert die API mit einem Token-Bucket, der per Workspace gilt. Die nachhaltigen Raten sind 10 Anfragen pro Sekunde im Free-Plan, 30 im Pro-Plan und 100 im Business-Plan, mit Burst-Kapazitäten von 20, 60 und 200. Im Pro-Plan erlaubt ein voller Bucket zum Beispiel 60 Anfragen auf einmal, bevor sich die Rate auf die nachhaltigen 30 pro Sekunde einpendelt. Obendrauf sitzt ein separater Bucket pro API-Key, sodass ein lauter Key nicht den ganzen Workspace auffrisst. Die meisten Link-Erstellungsjobs passen in den Burst und spüren das Limit gar nicht.

Sie müssen nicht raten, wann ein Limit greift. Eine abgelehnte Anfrage trägt drei Signale:

  • Retry-After - wie viele Sekunden Sie vor dem nächsten Versuch warten sollen (derzeit 1).
  • X-RateLimit-Scope - welcher Bucket gegriffen hat: workspace oder api_key.
  • Der JSON-Body - {"error":"workspace rate limit exceeded"}, der das Limit benennt.

Es gibt keine Header mit verbleibenden Token, also takten Sie clientseitig: halten Sie die Parallelität auf oder unter der nachhaltigen Rate Ihres Plans und halten Sie beim ersten 429 an und warten Sie auf Retry-After, statt in eine Wand aus Ablehnungen zu rennen. Vorausschauendes Pacing hält den Durchsatz glatt; blindes Wiederholen nach jeder Ablehnung verschwendet Roundtrips und erzeugt, wenn jeder Client im selben Moment wiederholt, eine Thundering Herd.

Ein Token-Bucket, der sich mit der Workspace-Rate auffüllt, während Anfragen Token verbrauchen, und eine 429-Antwort mit Retry-After-Header, sobald der Bucket leer ist

Wenn Sie viele Links erstellen müssen, loopen Sie nicht über den Single-Create-Endpunkt. POST /v1/workspaces/{workspace_id}/links/bulk nimmt bis zu 100 Links in einer Anfrage entgegen. Ein Bulk-Aufruf bewegt hundert Links in einem Roundtrip, während hundert Einzelaufrufe hundert Token und den Großteil Ihres Bursts verbrauchen. So bewegt der Google-Sheets-Import die Links einer ganzen Kampagne, ohne den Limiter auszulösen.

Ein 429 Too Many Requests - der Status, den RFC 6585 genau dafür reserviert - kommt mit einem Retry-After-Header zurück, der Ihnen sagt, wie viele Sekunden Sie warten sollen. Respektieren Sie ihn. Diese Zahl ist der Limiter, der Ihnen sagt, wann ein Token verfügbar ist, und das ist bessere Information als jede Schätzung Ihres Backoffs.

Retries: Welche Codes, und wie Sie Backoff betreiben

Nicht jeder Fehler ist es wert, erneut versucht zu werden, und den falschen Fehler zu wiederholen ist, wie aus einem kleinen Ausfall ein Ausfall wird. Sortieren Sie die Antworten in zwei Stapel.

Wiederholen Sie diese, weil sie transient sind: 429 (Sie waren zu schnell), sowie 500, 502, 503, 504 (ein server- oder gateweyseitiger Fehler, der sich von selbst klären könnte). Wiederholen Sie diese nicht, weil dieselbe Anfrage identisch scheitern wird: 400 (das Payload ist ungültig), 401 (der Token fehlt oder ist falsch), 403 (dem Token fehlt der Scope), 404 (die Ressource existiert nicht oder gehört Ihnen nicht) und 409 (ein Slug-Konflikt oder ein Bearbeitungskonflikt mit veralteter Version). Der erste Stapel bedeutet "warten Sie und versuchen Sie es erneut". Der zweite bedeutet "beheben Sie den Code oder die Eingabe". Ein 400 in einer engen Schleife zu wiederholen macht aus einem Bug einen Denial-of-Service-Angriff auf sich selbst.

Für die wiederholbaren Codes ist der relevante Algorithmus exponentielles Backoff mit Jitter. Reines exponentielles Backoff - die Wartezeit jedes Versuchs verdoppeln - synchronisiert dennoch Clients, weil jeder Client, der zum selben Moment scheiterte, auch zu denselben Momenten wiederholt. Zufälligkeit hinzuzufügen verteilt sie. AWSs Artikel zu Exponential Backoff and Jitter ist die kanonische Referenz und zeigt, warum die Jitter-Variante den Contention drastisch reduziert. Eine kompakte TypeScript-Version:

const RETRYABLE = new Set([429, 500, 502, 503, 504]);

async function withRetry<T>(
  call: () => Promise<Response>,
  max = 5,
): Promise<Response> {
  let attempt = 0;
  while (true) {
    const res = await call();
    if (res.ok || !RETRYABLE.has(res.status) || attempt >= max) return res;

    // Honor server guidance first; otherwise back off exponentially with full jitter.
    const retryAfter = Number(res.headers.get("retry-after"));
    const base =
      Number.isFinite(retryAfter) && retryAfter > 0
        ? retryAfter * 1000
        : Math.min(1000 * 2 ** attempt, 20_000);
    const wait = Math.random() * base; // full jitter
    await new Promise((r) => setTimeout(r, wait));
    attempt++;
  }
}

Drei Dinge machen das sicher statt gefährlich. Es begrenzt Versuche, sodass ein anhaltender Fehler laut scheitert statt ewig zu drehen. Es respektiert Retry-After, wenn der Server ihn sendet, und greift nur dann auf berechnetes Backoff zurück, wenn er das nicht tut. Und es jittert, sodass eine Flotte von Workern, die sich vom selben Einbruch erholen, nicht im Gleichschritt ansturmt. Bisher ist nur das Go-SDK veröffentlicht; die SDKs für TypeScript, Python und Ruby sind noch nicht in ihren Paket-Registries, daher ist dieser kleine Wrapper über schlichtes HTTP in diesen Sprachen der ganze Client, den Sie brauchen.

Es gibt eine Regel, die Retries mit dem nächsten Abschnitt verbindet: Ein Retry eines Create ist nur sicher, wenn das Create idempotent ist. Andernfalls riskiert jeder Retry einen zweiten Link.

Der klassische Fehler sieht so aus. Ihr Worker erstellt einen Kurzlink, der Link wird erstellt, aber die 200-Antwort kommt nie an - die Verbindung bricht auf dem Rückweg ab. Der Worker sieht einen Timeout, nimmt Fehler an und wiederholt. Jetzt haben Sie zwei Links für eine Kampagne. In großem Maßstab füllt sich das Dashboard mit /foo, /foo-1, /foo-2, und die Duplikate verzerren jeden nachgelagerten Report.

Idempotency-Keys schließen diese Lücke. Senden Sie einen Idempotency-Key-Header bei einer mutierenden Anfrage - einen beliebigen String bis zu 255 Zeichen - und der Server speichert die Antwort dagegen. Präsentieren Sie denselben Key erneut und Sie bekommen die ursprüngliche Antwort zurück, Status-Code und Body, ohne dass die Operation zweimal ausgeführt wird. Das Muster ist dasselbe, das Stripe für idempotente Anfragen dokumentiert, und es ist der Standardweg, um ein unzuverlässiges Netzwerk für Schreiboperationen sicher zu machen.

Das Detail, das den Unterschied macht, ist, woher der Key kommt. Generieren Sie keinen zufälligen Key pro Versuch - das unterläuft den Sinn, weil jeder Retry dann wie eine neue Operation aussieht. Leiten Sie ihn von einer stabilen Geschäfts-ID ab, sodass dieselbe logische Aktion stets denselben Key erzeugt:

const res = await withRetry(() =>
  fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `order-${order.id}-link`,
    },
    body: JSON.stringify({ destination_url: order.landingUrl }),
  }),
);
const link = await res.json();

Jetzt trägt ein Retry desselben Jobs order-12345-link erneut, trifft die gespeicherte Antwort und gibt den bereits existierenden Link zurück. Genau ein Link pro Bestellung, egal wie oft die Queue wieder ausliefert. Das ist es, was den oben stehenden Backoff-Loop sicher mit Creates kombinierbar macht: der Retry und der Idempotency-Key sind zwei Hälften derselben Garantie.

Eine At-least-once-Job-Queue, die zwei Create-Anfragen mit demselben Idempotency-Key abfeuert; der Server speichert die erste Antwort und gibt sie für die zweite zurück, sodass genau ein Link erstellt wird

Zwei Grenzen im Hinterkopf behalten. Der Key ist per Workspace gültig: derselbe Key in zwei Workspaces erstellt zwei Links - das ist korrekt für eine mandantenfähige API, überrascht aber Teams, die Keys als global annehmen. Und der Cache ist nicht unbegrenzt - bei Elido hält er 24 Stunden, verschlüsselt nach (workspace, key). Ein Retry innerhalb des Fensters dedupliziert; ein Retry drei Tage später, von einem feststeckenden Job, der endlich abgearbeitet wird, erstellt einen frischen Link. Verlassen Sie sich bei mehrtägigen Batches nicht allein auf den Key. Persistieren Sie die vom ersten Erfolg zurückgegebene Link-ID und schlagen Sie sie nach, bevor Sie erneut einen Create absetzen. Die IETF standardisiert diesen Header im Idempotency-Key-Draft, und der 24-Stunden-Fenster-Vorbehalt wird dort ebenfalls erwähnt.

Wenn Sie heute eine API-Integration verdrahten und wollen, dass sie ihre eigenen Retries überlebt: Starten Sie mit einem kostenlosen Workspace, generieren Sie ein Service-Account-Token und setzen Sie einen Idempotency-Key auf Ihren allerersten Create - anstatt ihn nachzurüsten, wenn die Duplikate auftauchen.

Alles zusammengefügt

Ein produktionsreifer Create-Aufruf stapelt die drei Mechanismen. Takten Sie den Client auf die nachhaltige Rate Ihres Plans, damit Sie selten ein 429 sehen. Hülle den Aufruf in jittertes Backoff, das nur die transienten Codes wiederholt und Retry-After respektiert. Tragen Sie einen Idempotency-Key, der von einer Geschäfts-ID abgeleitet ist, sodass der Retry sicher ist. Mit schlichtem fetch deckt der Wrapper oben die ersten beiden ab, und Sie liefern nur den Key:

export async function shortenForOrder(order: Order) {
  const res = await withRetry(() =>
    fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `order-${order.id}-link`,
      },
      body: JSON.stringify({
        destination_url: order.landingUrl,
        tags: [`order:${order.id}`],
      }),
    }),
  );
  if (res.status === 429) {
    // withRetry gave up and we are still limited. Defer the job.
    throw new RetryableJobError(Number(res.headers.get("retry-after")) || 1);
  }
  if (!res.ok) throw new Error(`create failed: ${res.status}`); // non-retryable
  return res.json();
}

Das ist nichts Exotisches. Es ist dieselbe Disziplin, die jede schreibintensive API verdient - angewandt auf Links. Die Belohnung ist eine Integration, die unter Last das Richtige tut, anstatt still Ihr Link-Inventar zu beschädigen. Für die Lese-Seite derselben API - Click-Daten herausziehen, ohne den Limiter zu belasten - sind die Kompromisse in Webhooks vs. Polling für Click-Tracking behandelt, und die vollständige Endpunkt-Oberfläche finden Sie auf der Seite API und SDKs sowie in der Entwickler-Lösungsübersicht.

Weitere Artikel im Blog

Häufig gestellte Fragen

Welche Rate Limits gelten für eine URL-Shortener-API?

Bei Elido gelten die Limits pro Workspace und hängen vom Plan ab, per Token-Bucket: 10 Anfragen pro Sekunde im Free-Plan (Burst 20), 30 im Pro-Plan (Burst 60) und 100 im Business-Plan (Burst 200). Obendrauf gibt es einen separaten Bucket pro API-Key. Greift ein Limit, antwortet die API mit 429 sowie den Headern Retry-After und X-RateLimit-Scope, der angibt, welcher Bucket getroffen wurde, sodass ein Client weiß, wie lange er warten muss. Bulk-Erstellungen nehmen bis zu 100 Links pro Anfrage, daher ist der Bulk-Endpunkt das richtige Werkzeug für große Jobs.

Welche API-Fehler sollte ich erneut versuchen?

Wiederholen nur bei transienten Fehlern: 429 (Rate Limit), 500, 502, 503 und 504. Verwenden Sie exponentielles Backoff mit Jitter und ein Retry-Cap. Niemals 400, 401, 403, 404 oder 409 wiederholen - das sind Validierungsfehler, Auth-Probleme oder Konflikte; dieselbe Anfrage wird erneut scheitern. Kombinieren Sie Retries mit einem Idempotency-Key, damit ein wiederholter Create keinen doppelten Link erzeugt.

Was ist ein Idempotency-Key und wann brauche ich einen?

Ein Idempotency-Key ist ein opaker String, den Sie bei einer mutierenden Anfrage mitsenden, damit der Server einen Retry erkennt und das ursprüngliche Ergebnis zurückgibt, anstatt zweimal zu handeln. Sie brauchen ihn immer dann, wenn ein Create oder Update wiederholt werden kann - was immer der Fall ist, weil Netzwerke ausfallen und Job-Queues mindestens einmal liefern. Leiten Sie den Key von einer stabilen Geschäfts-ID wie einer Bestellnummer ab, sodass dieselbe logische Operation stets denselben Key trägt.

Wie verhindere ich doppelte Kurzlinks?

Senden Sie bei jedem Create einen Idempotency-Key, der von einer stabilen Geschäfts-ID abgeleitet ist. Wenn der Aufruf wiederholt wird, gibt der Server den ursprünglich erstellten Link zurück, anstatt einen zweiten zu erzeugen. Bei Jobs, die Tage später erneut versucht werden - jenseits des Idempotenz-Cache-Fensters - speichern Sie außerdem die vom ersten Erfolg zurückgegebene Link-ID und schlagen Sie sie nach, bevor Sie den Create erneut absetzen.

Wie lange gilt ein Idempotency-Key?

Bei Elido lebt der Idempotenz-Cache 24 Stunden, verschlüsselt nach Workspace und Key. Ein Retry innerhalb dieses Fensters gibt die gespeicherte Antwort zurück; ein Retry danach wird als neue Anfrage behandelt und erstellt eine neue Ressource. Für mehrtägige Batch-Jobs sollten Sie nicht allein auf den Key vertrauen, sondern die zurückgegebene Link-ID beim ersten Erfolg persistieren.

Elido testen

URL einfügen, kurzer Link in Sekunden

Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.

Keine Anmeldung nötig. Kostenlos registrieren für 50 Links im Monat.

Elido testen

URL-Shortener mit EU-Hosting: eigene Domains, tiefe Analytik und eine offene API. Kostenloser Tarif - keine Kreditkarte nötig.

Tags
url shortener api rate limits
api idempotency key
retry with exponential backoff
429 too many requests
link shortener api
idempotent requests

Weiterlesen