--- title: "Error handling & rate limits" description: "Learn how to parse API errors, handle status codes, and implement retry logic." source_url: https://dev.digicert.com/get-started/error-handling-rate-limits.html --- DigiCert® ONE APIs return **JSON errors** and enforce per-API token **rate limits**. Understanding returned errors and rate limits helps you: - Parse error payloads and map them to actionable logic. - Respect rate limits to avoid throttling. - Implement production-grade retry and logging strategies. ## Error response structure and example **Error response structure** ```json { "error": { "code": "", "message": "" } } ``` - `code`: Returned error code. - `message`: Description of the error that occurred and potential solutions. **Error response example** ```json { "errors": [ { "code": "duplicate_error", "message": "Nickname api_service_user_3 already exists. Enter a different nickname." } ] } ``` ## API troubleshooting basics | Symptom | Likely cause | Quick fix | | --- | --- | --- | | 401 Unauthorized | Missing/invalid `x-api-key` or expired certificate | Verify the `x-api-key` header; confirm the service user is active. | | 403 Forbidden | Authenticated but lacks required role | Add the needed role in Account Manager or use a different key. | | 429 Too Many Requests | Burst traffic | Implement backoff; spread calls across service users. | | 503 Service Unavailable | Maintenance window | Retry after the maintenance window; escalate if it lasts longer than 10 minutes. | | SSL handshake failure when using mTLS | Wrong certificate, key mismatch, or incorrect URL | Check cert/key pair and ensure URL prefix is correct. | | Everything stopped at midnight | Key or service user reached end date | Create/rotate the key before expiry; update all integrations. | | Works in demo, fails in prod | Using demo key against prod hostname | Generate a new key in production; update `DC_BASE` environment variable. | ## HTTP status and machine codes | HTTP | Machine code | Meaning | Retry? | | --- | --- | --- | --- | | 200 OK | — | Success | — | | 201 Created | — | Resource created | — | | 204 No Content | — | Success, no body | — | | 400 Bad Request | `invalid_request` | Payload failed validation | No | | 401 Unauthorized | `invalid_api_key` | Bad or missing API token | No | | 403 Forbidden | `access_denied` | Authenticated but not authorized | No | | 404 Not Found | `resource_not_found` | Resource ID or path is wrong | No | | 409 Conflict | `conflict` | Resource already exists or version clash | No | | 429 Too Many Requests | `rate_limit_exceeded` | Rate limit hit | After `Retry-After` | | 500 Internal Server Error | `internal_error` | Unexpected server fault | With backoff | | 503 Service Unavailable | `service_unavailable` | Maintenance or downstream outage | After `Retry-After` | > **Tip** > > Always check for the **`Retry-After`** header on `429` and `503` responses. The value is either seconds or an HTTP date. ## Sample retry logic **Python** ``` import time import requests from typing import Dict, Any def api_request(url: str, headers: Dict[str, str], max_retries: int = 3) -> Any: """Simple retry with exponential backoff for API requests.""" for i in range(max_retries): try: response = requests.get(url, headers=headers) response.raise_for_status() return response.json() except requests.exceptions.RequestException as error: # Check if error is retryable status_code = getattr(error.response, 'status_code', 0) is_retryable = status_code in [429, 500, 503] is_last_attempt = i == max_retries - 1 if not is_retryable or is_last_attempt: raise # Wait before retrying: 1s, 2s, 4s... delay = 2 ** i time.sleep(delay) # Example: Get certificates from DigiCert certificates = api_request( 'https://api.digicert.com/v1/certificates', headers={'X-API-Key': os.environ['DIGICERT_API_KEY']} ) ``` **Java** ``` import java.io.IOException; import java.net.http.*; import java.time.Duration; public class ApiClient { private static final HttpClient client = HttpClient.newHttpClient(); public static String apiRequest(String url, String apiKey, int maxRetries) throws IOException, InterruptedException { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("X-API-Key", apiKey) .header("Content-Type", "application/json") .GET() .build(); for (int i = 0; i < maxRetries; i++) { try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() >= 200 && response.statusCode() < 300) { return response.body(); } // Check if error is retryable int status = response.statusCode(); boolean isRetryable = status == 429 || status == 500 || status == 503; boolean isLastAttempt = i == maxRetries - 1; if (!isRetryable || isLastAttempt) { throw new IOException("API request failed: " + status); } } catch (IOException | InterruptedException e) { if (i == maxRetries - 1) throw e; } // Wait before retrying: 1s, 2s, 4s... Thread.sleep((long) Math.pow(2, i) * 1000); } throw new IOException("Max retries exceeded"); } // Example: Get certificates from DigiCert public static void main(String[] args) throws Exception { String response = apiRequest( "https://api.digicert.com/v1/certificates", System.getenv("DIGICERT_API_KEY"), 3 ); System.out.println(response); } } ``` **Go** ``` package main import ( "fmt" "io" "net/http" "os" "time" ) // apiRequest makes an HTTP GET request with exponential backoff retry func apiRequest(url string, headers map[string]string, maxRetries int) ([]byte, error) { client := &http.Client{Timeout: 30 * time.Second} for i := 0; i < maxRetries; i++ { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } // Add headers for key, value := range headers { req.Header.Set(key, value) } resp, err := client.Do(req) if err != nil { if i == maxRetries-1 { return nil, err } time.Sleep(time.Duration(1<= 200 && resp.StatusCode < 300 { return io.ReadAll(resp.Body) } if !isRetryable || isLastAttempt { return nil, fmt.Errorf("API request failed: %d", resp.StatusCode) } // Wait before retrying: 1s, 2s, 4s... time.Sleep(time.Duration(1< ApiRequest(string url, string apiKey, int maxRetries = 3) { client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Add("X-API-Key", apiKey); for (int i = 0; i < maxRetries; i++) { try { var response = await client.GetAsync(url); if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } // Check if error is retryable var statusCode = (int)response.StatusCode; var isRetryable = Array.Exists(retryableStatusCodes, code => code == statusCode); var isLastAttempt = i == maxRetries - 1; if (!isRetryable || isLastAttempt) { response.EnsureSuccessStatusCode(); } } catch (HttpRequestException ex) { if (i == maxRetries - 1) throw; } // Wait before retrying: 1s, 2s, 4s... await Task.Delay((int)Math.Pow(2, i) * 1000); } throw new Exception("Max retries exceeded"); } // Example: Get certificates from DigiCert static async Task Main() { var apiKey = Environment.GetEnvironmentVariable("DIGICERT_API_KEY"); var result = await ApiRequest( "https://api.digicert.com/v1/certificates", apiKey ); Console.WriteLine(result); } } ``` **PowerShell** ``` function Invoke-ApiRequest { param( [string]$Url, [hashtable]$Headers, [int]$MaxRetries = 3 ) $retryableStatusCodes = @(429, 500, 503) for ($i = 0; $i -lt $MaxRetries; $i++) { try { $response = Invoke-RestMethod -Uri $Url -Headers $Headers -Method Get return $response } catch { $statusCode = $_.Exception.Response.StatusCode.value__ $isRetryable = $statusCode -in $retryableStatusCodes $isLastAttempt = $i -eq ($MaxRetries - 1) if (-not $isRetryable -or $isLastAttempt) { throw $_ } # Wait before retrying: 1s, 2s, 4s... $delay = [math]::Pow(2, $i) Write-Host "Retrying in $delay seconds..." Start-Sleep -Seconds $delay } } } # Example: Get certificates from DigiCert $headers = @{ 'X-API-Key' = $env:DIGICERT_API_KEY 'Content-Type' = 'application/json' } $certificates = Invoke-ApiRequest ` -Url 'https://api.digicert.com/v1/certificates' ` -Headers $headers ` -MaxRetries 3 $certificates | ConvertTo-Json ```