Tibber LogoData API

Retry & backoff

Resilient retry patterns with full jitter examples

Use retries sparingly for transient 429 / 500 / 502 / 503 responses only. Permanent errors (400 / 401 / 403 / 404) must not be retried.

Retry & backoff snippets

Use these as starting points; tailor them to your use case.

TypeScript example

async function retry<T>(action: () => Promise<T>, opts: {
  retries?: number;
  baseMs?: number;   // initial delay
  factor?: number;   // exponential factor
  maxMs?: number;    // cap
  shouldRetry?: (e: any) => boolean;
} = {}): Promise<T> {
  const {
    retries = 5,
    baseMs = 400,
    factor = 2,
    maxMs = 15_000,
    shouldRetry = (e) => {
      if (!e || !e.response) return false;
      const s = e.response.status;
      return s === 429 || (s >= 500 && s < 600);
    },
  } = opts;

  let attempt = 0;
  while (true) {
    try {
      return await action();
    } catch (e: any) {
      if (attempt >= retries || !shouldRetry(e)) throw e;
      const expo = Math.min(baseMs * Math.pow(factor, attempt), maxMs);
      const jitter = Math.random() * expo; // Full jitter
      await new Promise(r => setTimeout(r, jitter));
      attempt++;
    }
  }
}

// Usage
const client = async () => {
  return fetch('https://data-api.tibber.com/v1/homes', {
    headers: { 'Authorization': `Bearer ${token}`, 'User-Agent': 'MyIntegration/1.0.0 tibber-data-js/0.0.1' }
  }).then(r => {
    if (!r.ok) {
      const err: any = new Error(`HTTP ${r.status}`);
      err.response = r;
      throw err;
    }
    return r.json();
  });
};

retry(client).then(console.log).catch(console.error);

C# example (.NET 8 minimal)

(Without external dependencies; adapt to Polly if you prefer.)

static async Task<T> RetryAsync<T>(Func<Task<T>> action, int retries = 5, int baseMs = 400, int maxMs = 15_000)
{
    for (var attempt = 0; ; attempt++)
    {
        try { return await action(); }
        catch (HttpRequestException ex) when (ShouldRetry(ex, attempt, retries))
        {
            var delay = CalcDelay(baseMs, attempt, maxMs);
            await Task.Delay(delay);
        }
    }

    static bool ShouldRetry(HttpRequestException ex, int attempt, int retries)
    {
        if (attempt >= retries) return false;
        if (ex.StatusCode is HttpStatusCode.TooManyRequests or >= HttpStatusCode.InternalServerError) return true;
        return false;
    }

    static TimeSpan CalcDelay(int baseMs, int attempt, int maxMs)
    {
        var expo = Math.Min(baseMs * Math.Pow(2, attempt), maxMs);
        var jitter = Random.Shared.NextDouble() * expo; // full jitter
        return TimeSpan.FromMilliseconds(jitter);
    }
}

// Usage with HttpClient
using var http = new HttpClient();
http.DefaultRequestHeaders.UserAgent.ParseAdd("MyIntegration/1.0.0 tibber-data-dotnet/0.1.0");
http.DefaultRequestHeaders.Authorization = new("Bearer", token);

var homes = await RetryAsync(async () =>
{
    using var resp = await http.GetAsync("https://data-api.tibber.com/v1/homes");
    if (!resp.IsSuccessStatusCode)
        throw new HttpRequestException($"HTTP {(int)resp.StatusCode}", null, resp.StatusCode);
    return await resp.Content.ReadAsStringAsync();
});

Anti-patterns

  • Retrying forever
  • Retrying 400 / 401 / 403 / 404
  • Identical fixed delays (thundering herd risk)
  • Parallel floods of retries after a single incident

Next: see Troubleshooting or return to Rate limiting.

On this page