Skip to content
AI Engineering
Building with the API

Errors, retries and safe rendering

Real calls fail and real output is untrusted. Handle both before you ship.

Chapter 7: Building with the APILesson 5 of 11Level: StarterAbout 12 min
Words on this page (6)
backoff
: Waiting longer after each failed attempt before retrying, for example 1, 2, then 4 seconds.
jitter
: A little randomness added to retry waits so many clients do not retry at the same moment.
logarithm
: The power you raise a base to in order to get a number. log base 2 of 8 is 3 because 2 to the power 3 is 8.
API
: Application programming interface: a way for one program to call another, usually over HTTP.
git
: The standard tool for tracking changes to code.
prompt
: The input text you give a model, including instructions, examples and data.

Errors

Read the status code. 400: your request is malformed or too long, so retrying will not help. 401 or 403: the key is wrong or lacks access. 429: rate limit, so wait and retry with (1 second, 2, 4, with some random ). 500 or 529: the provider is overloaded, so retry a few times and then show a clear message. Set timeouts. the request id from the response headers, which providers ask for in support tickets. Show users a plain message, never the raw error.

Keys stay on the server

An key in browser code is a key anyone can copy. Call the provider from your backend, keep the key in an environment variable and rate-limit per user. Never commit keys to .

Rendering model output safely

Models often reply in markdown. If you convert it to HTML and insert it into the page, a prompt-injected or odd answer can contain a script tag or a link that runs code. Sanitize the HTML with a library, or render markdown with a safe renderer that does not allow raw HTML. Treat the output like user input, because it can be shaped by user input. Do the same before putting model text into a database query or a shell command.

How retry with backoff works

  1. Send the request. It fails with a retryable error such as 429 (rate limited) or an overloaded or 5xx server error.
  2. Wait about 1 second plus a small random amount, then try again.
  3. If it fails again, wait about 2 seconds, then about 4.
  4. Stop after a fixed number of tries and show the user a plain message.

Without jitter, the waits total 1 + 2 + 4 = 7 seconds across the three retries. Jitter adds a random fraction so many clients do not retry together. Always cap the number of attempts and the longest wait.

A second example: retrying makes it worse

A request fails with 400 because the is too long. Retrying five times sends the same oversized prompt five times and fails five times. The fix is to shorten the prompt. A 401 behaves the same way: the key is wrong, and repeating the call changes nothing. Retry only errors that can pass on their own: rate limits, overload and timeouts.

What goes wrong when you render output

Markdown converted to HTML with raw HTML allowed can carry a script tag. Symptom: odd pop-ups or a changed page layout after one particular answer. To confirm, test your renderer with a harmless string such as an image tag with a broken source and an alert handler. If the alert fires, the renderer is unsafe.

Links can use a "javascript:" address that runs code when clicked. A safe renderer blocks that address type.

Model text placed into a database query can change the query. Pass values separately from the query text (a parameterized query) so the database treats them as data.

Try it yourself

Write a function that returns the wait before each of 5 attempts. Without jitter it should return 1, 2, 4, 8, 16 seconds. Add jitter and run it three times to see the lists differ. Then feed your page the harmless test string above and check that nothing runs.

Common mistake

Showing raw error text to users. It can reveal internal details and it is unhelpful.

Check yourself

You get a 429 response. What should you do?

Where should an API key live?

A model's reply contains an HTML script tag and your page inserts it as HTML. What is the risk?

Go deeper

Spotted a mistake or something unclear? Report it on GitHub.

On this page