Wie Rate-Limits und Fair Usage funktionieren
Die Sally API kennt zwei verschiedene Grenzen, und sie verlangen zwei verschiedene Reaktionen: Rate-Limits schützen den Dienst vor zu vielen Anfragen, die Fair-Usage-Regel bestimmt, wie viel du transkribieren kannst.
Diese Seite zeigt, wie du die Grenzen deines Unternehmenskontos abliest, wie du die drei Arten von 429 auseinanderhältst, und eine Wiederholungsschleife mit wachsender Pause zum Kopieren. Die Beispiele funktionieren so, wie sie dastehen, sobald SALLY_TOKEN und DIRECTORY_ID gesetzt sind, siehe Schnellstart.
Schnellnavigation
- Rate-Limits
- Deine Grenzen ablesen
- Fair Usage bei der Transkription
- Wie du reagierst
- Mit Backoff wiederholen
1. Rate-Limits
Anfragen werden je Token begrenzt, Anfragen ohne Anmeldung je Client-IP. Jedes Unternehmenskonto hat zusätzlich eigene Grenzen. Wird eine Grenze überschritten, antwortet die API mit 429.
| Grenze | Gilt für | Wo du den Wert siehst |
|---|---|---|
| Je Token | Jede angemeldete Anfrage | Nicht veröffentlicht |
| Je Client-IP | Anfragen ohne Token | Nicht veröffentlicht |
| Je Unternehmenskonto und Minute | Alle Anfragen für ein Unternehmenskonto | maxApiRequestsPerMinute |
| Je Unternehmenskonto und Tag | Alle Anfragen für ein Unternehmenskonto | maxApiRequestsPerDay |
2. Deine Grenzen ablesen
Die Grenzen eines Unternehmenskontos liest du am Unternehmenskonto selbst ab. Mit der Feldauswahl enthält die Antwort nur, was du brauchst:
- cURL
- JavaScript
- Python
curl "https://api.sally.io/v1.0/directories/$DIRECTORY_ID?fields=name,maxApiRequestsPerMinute,maxApiRequestsPerDay" \
-H "Authorization: Bearer $SALLY_TOKEN"
const url = new URL(`https://api.sally.io/v1.0/directories/${directoryId}`);
url.searchParams.set('fields', 'name,maxApiRequestsPerMinute,maxApiRequestsPerDay');
const response = await fetch(url, {
headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`},
});
const {maxApiRequestsPerMinute, maxApiRequestsPerDay} = await response.json();
import os
import requests
response = requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}",
params={"fields": "name,maxApiRequestsPerMinute,maxApiRequestsPerDay"},
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
)
limits = response.json()
{
"name": "Example GmbH",
"maxApiRequestsPerMinute": 60,
"maxApiRequestsPerDay": 10000
}
| Feld | Typ | Bedeutung |
|---|---|---|
maxApiRequestsPerMinute | number | Höchstzahl der API-Anfragen je Minute für dieses Unternehmenskonto. |
maxApiRequestsPerDay | number | Höchstzahl der API-Anfragen je Tag für dieses Unternehmenskonto. |
Die Werte oben sind Beispiele. Lies die echten für dein Unternehmenskonto ab, und lies sie ab und zu neu, statt sie fest einzutragen. GET /v1.0/directories liefert dieselben Felder für jedes Unternehmenskonto, das dein Token ansprechen kann.
Referenz: GET/v1.0/directories/{directoryId}
Verteil deine Aufrufe, statt sie gebündelt abzuschicken. Steht maxApiRequestsPerMinute auf 60, bleibt eine Anfrage pro Sekunde unter der Grenze, solange nichts anderes gleichzeitig dasselbe Unternehmenskonto nutzt.
3. Fair Usage bei der Transkription
Uploads zur Transkription unterliegen zusätzlich einer Fair-Usage-Regel. Sie gilt gleichermaßen für den zweistufigen Upload und für den Import per URL. Die API meldet sie mit eigenen Codes im Fehler-Body:
code | Bedeutung | Wann er kommt |
|---|---|---|
FUP_LIMIT_EXCEEDED | Das Transkriptionskontingent ist aufgebraucht. | Schon bei der Upload-Anfrage, bevor du eine Datei schickst. |
TOO_MANY_CONCURRENT_TRANSCRIPTIONS | Zu viele Transkriptionen deines Unternehmenskontos laufen gleichzeitig. | Etwa beim Abschließen eines Uploads. |
{
"statusCode": 429,
"message": "<für Menschen lesbarer Text>",
"code": "FUP_LIMIT_EXCEEDED"
}
Die Endpunkte, die eine Transkription starten:
- POST
…/recordings/uploadsStartet einen Aufnahme-Upload (Schritt 1/2) und gibt eine SAS-PUT-URL zurückAusprobieren - POST
…/recordings/uploads/{uploadId}/finalizeSchließt einen Aufnahme-Upload ab (Schritt 2/2) und erstellt die AufnahmeAusprobieren - POST
…/recordings/from-urlErstellt eine Aufnahme aus einer Datei-URL (kein Upload nötig)Ausprobieren
4. Wie du reagierst
Behandle die Fälle getrennt. Ein einfaches 429 heißt langsamer machen und später erneut versuchen. TOO_MANY_CONCURRENT_TRANSCRIPTIONS heißt warten, bis laufende Transkriptionen fertig sind. FUP_LIMIT_EXCEEDED heißt, das Kontingent ist aufgebraucht, und ein baldiger neuer Versuch bringt dieselbe Antwort. Wie der Fehler-Body aussieht, steht unter Fehler.
| 429 mit | Bedeutung | Reaktion |
|---|---|---|
keinem code | Rate-Limit überschritten | Warten, dann mit wachsender Pause erneut versuchen. |
TOO_MANY_CONCURRENT_TRANSCRIPTIONS | Zu viele laufende Transkriptionen | Warten, bis laufende Transkriptionen fertig sind, dann erneut versuchen. |
FUP_LIMIT_EXCEEDED | Kontingent aufgebraucht | Nicht bald erneut versuchen. Uploads stoppen und einem Menschen Bescheid geben. |
5. Mit Backoff wiederholen
Ein 429 sagt dir nicht, wie lange du warten sollst. Warte deshalb nach jedem Versuch etwas länger: eine Sekunde, dann zwei, vier und so weiter, höchstens eine Minute, mit einem kleinen Zufallsanteil, damit mehrere Clients nicht im Gleichschritt wiederholen. Dieselbe Schleife passt für einen 500. FUP_LIMIT_EXCEEDED wird sofort durchgereicht.
- cURL
- JavaScript
- Python
wait=1
for attempt in 1 2 3 4 5 6; do
response=$(curl -s -w '\n%{http_code}' \
"https://api.sally.io/v1.0/directories/$DIRECTORY_ID/recordings" \
-H "Authorization: Bearer $SALLY_TOKEN")
status=$(echo "$response" | tail -n 1)
body=$(echo "$response" | sed '$d')
if [ "$status" = "429" ] && [ "$(echo "$body" | jq -r '.code')" = "FUP_LIMIT_EXCEEDED" ]; then
echo "Kontingent aufgebraucht, kein neuer Versuch" >&2; break
fi
if [ "$status" != "429" ] && [ "$status" -lt 500 ]; then
echo "$body"; break
fi
sleep "$wait"
wait=$(( wait * 2 > 60 ? 60 : wait * 2 ))
done
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function withRetry(request, maxAttempts = 6) {
let wait = 1000;
for (let attempt = 1; ; attempt++) {
const response = await request();
const retryable = response.status === 429 || response.status >= 500;
if (!retryable || attempt === maxAttempts) return response;
if (response.status === 429) {
const body = await response.clone().json().catch(() => ({}));
if (body.code === 'FUP_LIMIT_EXCEEDED') return response; // Kontingent aufgebraucht, nicht wiederholen
}
await sleep(wait + Math.random() * 250);
wait = Math.min(wait * 2, 60_000);
}
}
const response = await withRetry(() =>
fetch(`https://api.sally.io/v1.0/directories/${directoryId}/recordings`, {
headers: {Authorization: `Bearer ${process.env.SALLY_TOKEN}`},
}),
);
import os
import random
import time
import requests
def with_retry(request, max_attempts=6):
wait = 1
for attempt in range(1, max_attempts + 1):
response = request()
retryable = response.status_code == 429 or response.status_code >= 500
if not retryable or attempt == max_attempts:
return response
if response.status_code == 429:
try:
code = response.json().get("code")
except ValueError:
code = None
if code == "FUP_LIMIT_EXCEEDED":
return response # Kontingent aufgebraucht, nicht wiederholen
time.sleep(wait + random.random() / 4)
wait = min(wait * 2, 60)
response = with_retry(lambda: requests.get(
f"https://api.sally.io/v1.0/directories/{directory_id}/recordings",
headers={"Authorization": f"Bearer {os.environ['SALLY_TOKEN']}"},
))
Bei TOO_MANY_CONCURRENT_TRANSCRIPTIONS sind ein paar Sekunden Pause oft zu kurz. Warte, bis deine laufenden Transkriptionen isTranscriptionCompleted melden, oder nutze einen Webhook, bevor du den nächsten Upload abschließt.