Skip to content

Retries & Errors

Retries

Failed requests are retried automatically. RetryPolicy's defaults match TypeSafe's official Python and JS SDKs:

Setting Default Meaning
maxRetries 2 retries after the first attempt; 0 disables retries
retryStatuses 408, 429, 500–599 HTTP statuses worth retrying, including 529 Overloaded
retryOnConnectionError true retry when no response arrives (DNS, refused or dropped connections)
retryOnTimeout true retry an attempt that exceeded timeout
initialBackoff 0.5 s first delay, doubling on each retry
maxBackoff 5 s cap on the delay
jitter 0.25 subtract up to this fraction of each delay at random
respectRetryAfter true honor the server's retry-after-ms / Retry-After header
maxRetryAfter 60 s ignore server hints longer than this and use backoff instead

RetryPolicy.NONE turns retries off. The Configuration page shows common variations.

Other 4xx errors, such as a bad key or an invalid request, are never retried: retrying can't fix them.

Errors

Every failure of a request or a response is a JevException:

Exception When
JevConfigException the client can't be configured, e.g. no API key
JevValidationException the request broke a local rule; problems lists every issue, and nothing was sent
JevApiException a non-2xx response after retries, carrying status, body, bodyJson, headers, requestId and endpoint
JevBadRequestException 400
JevAuthenticationException 401: missing or invalid API key
JevPermissionDeniedException 403
JevNotFoundException 404
JevUnprocessableEntityException 422: the server rejected the request; body names the field
JevRateLimitException 429; retryAfter is the server's hint, if it sent one
JevInternalServerException 5xx
↳ ↳ JevOverloadedException 529: TypeSafe is temporarily overloaded
JevResponseValidationException a 2xx response that was malformed or didn't match the questions; fieldPath locates it
JevConnectionException no response at all: DNS, TLS, or a refused or dropped connection
JevTimeoutException an attempt exceeded timeout

Messages include the status and request id, and never the API key.

Misusing a result is a programming error, not a JevException: asking for an id or handle that wasn't in the request, or reading a Noul as a Choice, throws IllegalArgumentException.

Handling errors

suspend fun triageOrNull(
    jev: JevApi,
    ticket: String,
): JevResult? =
    try {
        jev.ask(Triage, state = ticket)
    } catch (e: JevAuthenticationException) {
        throw IllegalStateException("Check TYPESAFE_API_KEY", e)
    } catch (e: JevRateLimitException) {
        // Already retried per the RetryPolicy; back off further before trying again.
        println("rate limited; server suggests waiting ${e.retryAfter}")
        null
    } catch (e: JevTimeoutException) {
        println("timed out: ${e.message}")
        null
    } catch (e: JevConnectionException) {
        println("no response from TypeSafe: ${e.message}")
        null
    } catch (e: JevApiException) {
        // Every other HTTP error: status, raw body, and the request id for support.
        println("HTTP ${e.status} (request ${e.requestId}): ${e.body}")
        null
    }

Or map any failure to a description with a when:

when (e) {
    is JevValidationException -> "invalid request: ${e.problems}"
    is JevUnprocessableEntityException -> "server rejected the request: ${e.bodyJson}"
    is JevOverloadedException -> "TypeSafe is overloaded (529); try again shortly"
    is JevResponseValidationException -> "unusable response at ${e.fieldPath}"
    is JevApiException -> "HTTP ${e.status} from ${e.endpoint}"
    is JevConnectionException -> "network problem: ${e.message}"
    else -> e.message ?: e.toString()
}

Validation errors

Local validation reports every problem at once, before anything is sent:

try {
    jev.query(state = "Is this spam?") {
        noul("spam", "")                                             // blank instructions
        score("severity", "How severe?") { level("only one level") } // a Score needs 2..10 levels
    }
} catch (e: JevValidationException) {
    // Nothing was sent. Every problem is listed at once.
    e.problems.forEach(::println)
}