Skip to content

Error handling

This page shows practical patterns for handling non-2xx responses, timeouts, retries, and pagination.

For the canonical schema, see The error object.

Handle non-2xx responses

bash
curl https://api.fastapi.ai/v1/responses \
  -H "Authorization: Bearer $FAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4.1","input":"Hello"}' \
  -i
javascript
const res = await fetch('https://api.fastapi.ai/v1/responses', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.FAST_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ model: 'gpt-4.1', input: 'Hello' }),
});

if (!res.ok) {
  const text = await res.text();
  throw new Error(`HTTP ${res.status}: ${text}`);
}

console.log(await res.json());
python
import os
import requests

resp = requests.post(
  "https://api.fastapi.ai/v1/responses",
  headers={
    "Authorization": f"Bearer {os.environ['FAST_API_KEY']}",
    "Content-Type": "application/json",
  },
  json={"model": "gpt-4.1", "input": "Hello"},
)

if resp.status_code >= 400:
  raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")

print(resp.json())
go
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
)

func main() {
  payload := map[string]any{"model": "gpt-4.1", "input": "Hello"}
  b, _ := json.Marshal(payload)

  req, _ := http.NewRequest("POST", "https://api.fastapi.ai/v1/responses", bytes.NewReader(b))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  body, _ := io.ReadAll(resp.Body)
  if resp.StatusCode >= 400 {
    panic(fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)))
  }

  fmt.Println(string(body))
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
  public static void main(String[] args) throws Exception {
    String body = "{\"model\":\"gpt-4.1\",\"input\":\"Hello\"}";

    HttpRequest req = HttpRequest.newBuilder()
      .uri(URI.create("https://api.fastapi.ai/v1/responses"))
      .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

    HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
    if (resp.statusCode() >= 400) {
      throw new RuntimeException("HTTP " + resp.statusCode() + ": " + resp.body());
    }
    System.out.println(resp.body());
  }
}

Timeouts

bash
curl https://api.fastapi.ai/v1/responses \
  -H "Authorization: Bearer $FAST_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 30 \
  -d '{"model":"gpt-4.1","input":"Hello"}'
javascript
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 30_000);

try {
  const res = await fetch('https://api.fastapi.ai/v1/responses', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.FAST_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'gpt-4.1', input: 'Hello' }),
    signal: controller.signal,
  });

  console.log(res.status, await res.text());
} finally {
  clearTimeout(t);
}
python
import os
import requests

resp = requests.post(
  "https://api.fastapi.ai/v1/responses",
  headers={
    "Authorization": f"Bearer {os.environ['FAST_API_KEY']}",
    "Content-Type": "application/json",
  },
  json={"model": "gpt-4.1", "input": "Hello"},
  timeout=30,
)

print(resp.status_code, resp.text)
go
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "time"
)

func main() {
  payload := map[string]any{"model": "gpt-4.1", "input": "Hello"}
  b, _ := json.Marshal(payload)

  client := &http.Client{Timeout: 30 * time.Second}
  req, _ := http.NewRequest("POST", "https://api.fastapi.ai/v1/responses", bytes.NewReader(b))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  resp, err := client.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  body, _ := io.ReadAll(resp.Body)
  fmt.Println(resp.StatusCode, string(body))
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Main {
  public static void main(String[] args) throws Exception {
    String body = "{\"model\":\"gpt-4.1\",\"input\":\"Hello\"}";

    HttpClient client = HttpClient.newBuilder()
      .connectTimeout(Duration.ofSeconds(30))
      .build();

    HttpRequest req = HttpRequest.newBuilder()
      .uri(URI.create("https://api.fastapi.ai/v1/responses"))
      .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
      .header("Content-Type", "application/json")
      .timeout(Duration.ofSeconds(30))
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

    HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
    System.out.println(resp.statusCode() + " " + resp.body());
  }
}

Retries (basic)

Retry is typically only safe for idempotent requests (for example, GET and some POSTs if you use your own idempotency keying).

The most common retry candidates are transient failures:

  • 429 (rate limits)
  • 500, 502, 503, 504 (server/network issues)
bash
url="https://api.fastapi.ai/v1/responses"
payload='{"model":"gpt-4.1","input":"Hello"}'

for i in 0 1 2 3 4; do
  resp=$(curl -s -w "\n%{http_code}" "$url" \
    -H "Authorization: Bearer $FAST_API_KEY" \
    -H "Content-Type: application/json" \
    -d "$payload")
  code=$(echo "$resp" | tail -n1)
  body=$(echo "$resp" | sed '$d')

  if [ "$code" -lt 400 ]; then
    echo "$body"
    break
  fi

  case "$code" in
    429|500|502|503|504)
      case "$i" in
        0) delay=0.2 ;;
        1) delay=0.4 ;;
        2) delay=0.8 ;;
        3) delay=1.6 ;;
        *) delay=2 ;;
      esac
      sleep "$delay"
      ;;
    *) echo "HTTP $code: $body" && exit 1 ;;
  esac
done
javascript
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function postWithRetry(url, body, { max = 5 } = {}) {
  for (let attempt = 0; attempt < max; attempt++) {
    const res = await fetch(url, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.FAST_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });

    if (res.ok) return res.json();

    const retryable = [429, 500, 502, 503, 504].includes(res.status);
    if (!retryable || attempt === max - 1) {
      throw new Error(`HTTP ${res.status}: ${await res.text()}`);
    }

    // Exponential backoff with cap.
    const delay = Math.min(2000, 200 * 2 ** attempt);
    await sleep(delay);
  }
}

console.log(
  await postWithRetry('https://api.fastapi.ai/v1/responses', { model: 'gpt-4.1', input: 'Hello' })
);
python
import os
import time
import requests

url = "https://api.fastapi.ai/v1/responses"
headers = {
  "Authorization": f"Bearer {os.environ['FAST_API_KEY']}",
  "Content-Type": "application/json",
}
payload = {"model": "gpt-4.1", "input": "Hello"}

for attempt in range(5):
  resp = requests.post(url, headers=headers, json=payload, timeout=30)
  if resp.status_code < 400:
    print(resp.json())
    break

  if resp.status_code not in (429, 500, 502, 503, 504) or attempt == 4:
    raise RuntimeError(f"HTTP {resp.status_code}: {resp.text}")

  time.sleep(min(2, 0.2 * (2 ** attempt)))
go
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "time"
)

func main() {
  url := "https://api.fastapi.ai/v1/responses"
  payload := map[string]any{"model": "gpt-4.1", "input": "Hello"}
  b, _ := json.Marshal(payload)

  for attempt := 0; attempt < 5; attempt++ {
    req, _ := http.NewRequest("POST", url, bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      panic(err)
    }
    body, _ := io.ReadAll(resp.Body)
    resp.Body.Close()

    if resp.StatusCode < 400 {
      fmt.Println(string(body))
      break
    }

    retryable := resp.StatusCode == 429 || resp.StatusCode == 500 || resp.StatusCode == 502 || resp.StatusCode == 503 || resp.StatusCode == 504
    if !retryable || attempt == 4 {
      panic(fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)))
    }

    time.Sleep(time.Duration(min(2000, 200*(1<<attempt))) * time.Millisecond)
  }
}

func min(a, b int) int {
  if a < b {
    return a
  }
  return b
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
  public static void main(String[] args) throws Exception {
    String url = "https://api.fastapi.ai/v1/responses";
    String body = "{\"model\":\"gpt-4.1\",\"input\":\"Hello\"}";
    HttpClient client = HttpClient.newHttpClient();

    for (int attempt = 0; attempt < 5; attempt++) {
      HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();

      HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
      int code = resp.statusCode();
      if (code < 400) {
        System.out.println(resp.body());
        break;
      }

      boolean retryable = code == 429 || code == 500 || code == 502 || code == 503 || code == 504;
      if (!retryable || attempt == 4) {
        throw new RuntimeException("HTTP " + code + ": " + resp.body());
      }

      long delay = Math.min(2000, 200L * (1L << attempt));
      Thread.sleep(delay);
    }
  }
}

Pagination (list endpoints)

Most list endpoints support limit and after. A typical response includes data, first_id, last_id, and has_more.

bash
curl "https://api.fastapi.ai/v1/files?limit=100" \
  -H "Authorization: Bearer $FAST_API_KEY"
javascript
let after;

while (true) {
  const qs = new URLSearchParams();
  qs.set('limit', '100');
  if (after) qs.set('after', after);

  const res = await fetch(`https://api.fastapi.ai/v1/files?${qs.toString()}`, {
    headers: { Authorization: `Bearer ${process.env.FAST_API_KEY}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);

  const page = await res.json();
  for (const item of page.data ?? []) {
    console.log(item.id);
  }

  if (!page.has_more) break;
  after = page.last_id;
}
python
import os
import requests

after = None
while True:
  params = {"limit": 100}
  if after:
    params["after"] = after

  resp = requests.get(
    "https://api.fastapi.ai/v1/files",
    headers={"Authorization": f"Bearer {os.environ['FAST_API_KEY']}"},
    params=params,
    timeout=30,
  )
  resp.raise_for_status()

  page = resp.json()
  for item in page.get("data", []):
    print(item["id"])

  if not page.get("has_more"):
    break
  after = page.get("last_id")
go
package main

import (
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "net/url"
  "os"
)

func main() {
  var after string

  for {
    qs := url.Values{}
    qs.Set("limit", "100")
    if after != "" {
      qs.Set("after", after)
    }

    req, _ := http.NewRequest("GET", "https://api.fastapi.ai/v1/files?"+qs.Encode(), nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      panic(err)
    }
    body, _ := io.ReadAll(resp.Body)
    resp.Body.Close()

    var page map[string]any
    _ = json.Unmarshal(body, &page)

    data, _ := page["data"].([]any)
    for _, item := range data {
      m, _ := item.(map[string]any)
      fmt.Println(m["id"])
    }

    hasMore, _ := page["has_more"].(bool)
    if !hasMore {
      break
    }
    after, _ = page["last_id"].(string)
  }
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
  public static void main(String[] args) throws Exception {
    String after = null;
    HttpClient client = HttpClient.newHttpClient();

    while (true) {
      String url = "https://api.fastapi.ai/v1/files?limit=100" + (after != null ? "&after=" + after : "");

      HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
        .GET()
        .build();

      HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
      String body = resp.body();
      System.out.println(body);

      if (!body.contains("\"has_more\":true")) {
        break;
      }
      int idx = body.indexOf("\"last_id\":\"");
      if (idx == -1) break;
      int start = idx + 11;
      int end = body.indexOf("\"", start);
      if (end <= start) break;
      after = body.substring(start, end);
    }
  }
}

那年我双手插兜, 让bug稳如老狗