Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bavlio.com/llms.txt

Use this file to discover all available pages before exploring further.

Bavlio is honest about its current state. Three error envelope shapes coexist today (a unification migration is parked as a follow-up). Idempotency-Key middleware is not yet implemented. Here’s how to deal with both.

Error envelope shapes

Match on HTTP status code first; treat the body as supplementary detail. Newer endpoints conform to ADR-010 (flat shape with stable code); older endpoints still emit FastAPI defaults; validation failures emit a Pydantic-shaped envelope.

EpicHTTPError (flat)

Used by newer endpoints. The detail field is a stable machine-readable code; message is a human-readable explanation.
Example
{
  "detail": "VALIDATION_ERROR",
  "message": "Email is required"
}
Seen on: /api/v1/personalize, /api/v1/personalize/preview, /api/v1/email-finder/* (most routes).

Legacy HTTPException

Older FastAPI default. The detail field is a human-readable string; no separate code field. Treat detail as opaque text — match on status code and endpoint, not on detail content.
Example
{
  "detail": "Email is required"
}
Seen on: older endpoints that have not yet migrated to EpicHTTPError.

Pydantic request validation

Returned automatically when request bodies fail Pydantic validation. The details array contains structured per-field errors; path is the request path; message is constant.
Example
{
  "error": "Request Validation Error",
  "details": [
    {
      "loc": ["body", "email"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ],
  "path": "/api/v1/email-finder/search",
  "message": "The request failed validation"
}
Seen on: any endpoint with a Pydantic request body.

Status codes & retry rules

StatusMeaningRetry?
200OKn/a
201Createdn/a
202Accepted (async work queued)n/a
400Bad request — fix the body / paramsno
401Authentication failedno (re-auth)
403Authenticated but not allowedno
404Not foundno
409Conflict (duplicate, state change race)maybe (after read)
422Validation failedno
429Rate limitedyes (after Retry-After)
500Server erroryes (exponential backoff)
502Bad gatewayyes
503Service unavailableyes
504Gateway timeoutyes
A safe default agent: retry 429 honoring Retry-After, retry 5xx with exponential backoff (max 5 attempts), surface everything else immediately.

Retry pattern

Drop-in helper that respects Retry-After and backs off exponentially.
Python
import time
import httpx

def call_with_backoff(client, method, path, *, max_retries=5, **kwargs):
    """Retry on 429 and 5xx with exponential backoff. Stop on 4xx others."""
    for attempt in range(max_retries):
        response = client.request(method, path, **kwargs)
        if response.status_code < 400:
            return response
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", "1"))
            time.sleep(retry_after)
            continue
        if 500 <= response.status_code < 600:
            time.sleep(2 ** attempt)
            continue
        response.raise_for_status()
    response.raise_for_status()

Idempotency

The Idempotency-Key header is not yet honored by Bavlio. Sending it has no effect server-side. A retried POST after a network failure will create a duplicate.
Workaround until the middleware ships: after a failed mutation, follow up with a read before retrying. For example, after a failed POST /api/v1/campaigns/, call GET /api/v1/campaigns/?name=<your-name> to check whether your campaign was already created. Match on a stable client-side correlation field (campaign name, lead-set hash, etc.). Roadmap: a FastAPI middleware that accepts Idempotency-Key on POST/PUT/PATCH/DELETE, hashes the request, stores the response in Redis for 24 hours, and returns the cached response on retry.

FAQ

Retry on 429 (after waiting Retry-After seconds) and on 5xx server errors with exponential backoff. Do NOT retry on 4xx (other than 429) — fix the request and try once. Check error.detail or error.message for the specific cause.
Three envelopes coexist today. Newer endpoints use a flat shape: { detail: <code>, message: <text> } per ADR-010 (EpicHTTPError). Older endpoints use the FastAPI default: { detail: <text> }. Pydantic validation failures return { error, details, path, message }. Match on status code first.
401 means the API key is missing, malformed, or revoked. Re-authenticate. 403 means the key is valid but lacks permission for the operation — for example, attempting to mint a new API key with an API key (only Supabase JWTs can mint keys).