OpenAI Moderation API Rate Limits: Queue-Based Candidate Scoring Error Handling A developer outlined a queue-worker design for handling OpenAI moderation API rate-limit errors, using bounded retries with exponential backoff and jitter, honoring Retry-After headers, and capping both delay and attempt count at four. The approach carries a stable decision ID and candidate/rubric revisions across retries so late responses cannot produce duplicate scores, and separates transport failures, malformed structured output, and 429s into distinct states. The developer recommends alerting on queue age and retry exhaustion rather than on individual provider rejections, favoring a visible delayed state over infinite retries. For an OpenAI moderation API rate limit error, the least complex reliable design is a bounded retry inside a queue worker, with the candidate revision and rubric revision carried through every attempt. A 429 is scheduling input. It is not permission to let every layer retry at once. The page that wakes the on-call usually says the scoring queue is old, not that a provider rejected request 17. Work is accumulating, review decisions are late, and a retry loop may be making the queue worse. For an edtech hiring workflow, I want one answer first: is this candidate revision waiting for capacity, or is it permanently invalid? That distinction determines the next action. Start from the visible failure and work backward. Record the HTTP status, Retry-After when present, rate-limit headers, request ID, tenant, candidate revision, rubric revision, and attempt number together. Usage counters belong beside them. A log containing only 429 cannot tell an operator whether one school consumed the available window, all workers are too aggressive, or retries are multiplying the load. The earlier signal is queue age. Alert on age and retry exhaustion, then use provider headers to tune the worker rate. A short provider rejection is not automatically an incident; a candidate revision that has exceeded its bounded wait is a workflow incident. That threshold has a real cost in both directions. Set it too low and normal quota windows page someone. Set it too high and applicants wait without an explanation. I prefer a visible delayed state and a terminal review state over infinite retries. The system should say what it knows. No cleverness here. Four attempts is a policy, not a promise that the provider will recover. For a rate-limit response, use exponential backoff with jitter and honor Retry-After if the response supplies it. Cap the delay and the attempt count. Keep the same decision ID across retries so a late response cannot create a second score. Transport failures, malformed structured output, and a 429 are different states; they should not share one unbounded exception path. Here is the policy boundary I use in a worker. The request owns the provider call. The retry loop owns waiting, cancellation, and the decision about whether another attempt is allowed. The example uses Infrai's OpenAI-compatible surface for the structured moderation fallback; it does not pretend that the consolidated surface is a dedicated moderation endpoint. package main import "bytes" "context" "encoding/json" "fmt" "io" "math/rand" "net/http" "os" "strconv" "time" func moderateAndScore ctx context.Context, candidate string byte, error { requestBody := map string any{ "model": os.Getenv "INFRAI MODEL" , "messages": map string string{ {"role": "user", "content": candidate}, }, } payload, err := json.Marshal requestBody if err = nil { return nil, err } baseURL := os.Getenv "INFRAI BASE URL" for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequestWithContext ctx, http.MethodPost, baseURL+"/chat/completions", bytes.NewReader payload if err = nil { return nil, err } req.Header.Set "Authorization", "Bearer "+os.Getenv "INFRAI API KEY" req.Header.Set "Content-Type", "application/json" resp, err := http.DefaultClient.Do req if err = nil { return nil, err } body, readErr := io.ReadAll resp.Body resp.Body.Close if readErr = nil { return nil, readErr } if resp.StatusCode = http.StatusTooManyRequests { if resp.StatusCode < 200 || resp.StatusCode = 300 { return nil, fmt.Errorf "provider status %d: %s", resp.StatusCode, body } return body, nil } delay := 500 time.Millisecond time.Duration 1<