Retry requests safely
A broken connection or a 5xx answer can leave you unsure whether a request ran. This guide shows how to send it again so that the API does the work and charges your Budget once. Errors describes each answer.
Send a request again safely
POST /v1/completions, POST /v1/completion-jobs, POST /v1/harvest, and every Loom write take the Idempotency-Key header. A cancel of a Job or a harvest needs none: a second cancel answers the Job as it is.
- Make a new key for each new request, such as a UUID. A key has 1 to 255 characters.
- Send it in the
Idempotency-Keyheader. - If the request fails or the connection breaks, send the same request with the same key again.
KEY=$(uuidgen)
curl "$ACS_API_BASE/completions" \
-H "Authorization: Bearer $ACS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{"model": "llama-8b", "prompt": "The sky is", "max_tokens": 2}'uuidgen makes the key in the shell. Run the curl command again with the same KEY, and it prints the same Completion. The Python program prints True: its second request got the first request's Completion, and Inference ran once. In the openai SDK, extra_headers sends the header, and the SDK sends it again with each of its own tries.
With a key, the API answers each repeat of a request in this way:
- After the first request finished, the same request gets the first answer. A change to your Budget or to the served models since then does not change it. A Completion comes with the first answer's
X-Acs-Max-Tokens-Clampedheader. A Job, a Loom, or a Branch comes as it is now, and a Job comes with 200 in place of 202. - While the first request still runs, the same request gets 409 with the code
runningandRetry-After: 1. - A stream runs once. The same stream request again gets 409
no_answerwithx-should-retry: false, and the API does not run it or charge it again. Send a new key to run it again. - A Completion without a stream runs on when your client goes away, and pays only for its usage. The same request again gets its answer.
- A request that failed before the API charged it, for example with 429
budget_exceededor an error from Inference, frees its key. The same request with the key runs. - Another request with the same key, or the same key on another route, gets 409
idempotency_conflictwithx-should-retry: false.
The API keeps each key for 24 hours. All your API keys share one set of keys, and another User's keys are apart from yours. A request without the header runs each time that it arrives.
Which errors to try again
Send the same request again, with the same Idempotency-Key, after these answers:
- 429
rate_limitedorqueue_full: wait for the time in theRetry-Afterheader. A full Job cap is different: it answers 429job_capacitywithx-should-retry: false, so submit again when one of your Jobs ends. - 429
harvest_concurrency_exceeded: wait for the time in theRetry-Afterheader, or until one of your harvests ends. - 429
harvest_capacity_exceeded: wait for the time in theRetry-Afterheader. - 409
running: wait 1 second. - 503
db_pool_exhaustedorcircuit_open: wait for the time in theRetry-Afterheader. It is 1 second when the API's database was busy, and up to 60 seconds while the API holds a model back. Nothing ran, and you pay nothing. - Any error with
retryable: true, such as 502upstream_unreachableorvllm_oom: waitretry_after_seconds, the same as theRetry-Afterheader. - 504
upstream_deadline: wait a few minutes. Inference did not answer for 14 minutes, so another try at once waits as long, and starts the work on a GPU again.
After these answers, check or change the request before you send it again:
- 500
internal_error, withx-should-retry: false: the API failed, and the work may have run. If the request had anIdempotency-Key, send it again with the same key. It gets the first answer if the work finished, and the API does not charge it twice. Without a key, look for the result first, for example inGET /v1/looms. Report the error with itsX-Request-Id. - 429
budget_exceeded: ask for fewer tokens, with a smallermax_tokensor fewer prompts or choices, or ask for more tokens. - 429
spend_cap: new GPU work is paused (Spend limit). Try again after theRetry-Afterheader's hour. - 409
no_answer: the stream ran already. Send a new key to run it again. - 409
idempotency_conflict: the key was used with another request. Send a new key with each new request. - 429
harvest_quota_exceeded: the API key started its most harvests this month. Submit the Job in the next UTC month. - 429
spend_cap: the API paused new harvests to limit GPU spending. Wait for the time in theRetry-Afterheader. - Any other 4xx status. Fix what
messageandparamname.
Status codes lists every answer.
Set the client for long requests
A Completion can wait while Inference starts a cold model, and a long one runs for minutes. The API waits up to 14 minutes for Inference (Cold starts and long requests).
- Create the client with
timeout=900. The SDK then waits 900 seconds, longer than the API's 14 minutes. - Create it with
max_retries=0. Each try that the SDK sends after a broken connection starts the work on a GPU again. Without a key, a cut Completion without a stream also pays its whole Hold. - Send each request with an
Idempotency-Key, and send it again yourself after a broken connection. - Check the answer for
error. After 150 seconds, a Completion without a stream gets its 200 status, and a later failure comes as the body.
import os
import uuid
from openai import OpenAI
client = OpenAI(
base_url=os.environ["ACS_API_BASE"],
api_key=os.environ["ACS_API_KEY"],
timeout=900,
max_retries=0,
)
headers = {"Idempotency-Key": str(uuid.uuid4())}
completion = client.completions.create(
model="llama-8b", prompt="The sky is", max_tokens=2, extra_headers=headers
)
error = getattr(completion, "error", None)
if error:
print(error["message"])
else:
print(completion.choices[0].text)The SDK raises no exception for an error in the body of a 200 answer. It returns a Completion whose error field holds the error object. With curl, set --max-time above 840 seconds, or leave it out.
Retries in the openai SDK
The openai SDK sends a request again by itself, two more times by default, after:
- a connection error or a timeout
- 408, 409, and 429 answers, and every 5xx answer
Before each try, it waits as long as the retry-after-ms or Retry-After header says, up to 2 minutes. Without either header, it waits about half a second, then twice as long each time, up to 8 seconds.
It does not try again after an answer with x-should-retry: false, or with a Retry-After of more than 2 minutes. The API sends x-should-retry: false with these answers, so the SDK raises them at once:
- 409 for a Node that is not in a state for the action, 409
harvest_not_cancellable, 409idempotency_conflict, and 409no_answer - 429
budget_exceeded, 429job_capacity, 429harvest_quota_exceeded, and 429spend_cap - its own 500
- 413
request_too_large, 504upstream_deadline, and each error of Inference whoseretryableisfalse
To raise every error at its first answer, create the client with max_retries=0. Errors in the openai SDK lists the error classes.