Skip to content

Errors

How the TwinCell REST API reports failures and how the deeplife SDK maps them to Python exceptions.

Authentication

Every request authenticates with the X-API-Key header. Authorization: Bearer … is not accepted and returns 401 (AuthenticationError). The SDK sets the header from api_key= / DEEPLIFE_API_KEY; you only supply it yourself when calling the REST routes directly.

curl -H "X-API-Key: $DEEPLIFE_API_KEY" \
  https://open-deeplife-api.deeplife.co/v1/predictions

REST error shape

Most error responses use FastAPI’s standard JSON body:

{"detail": "Human-readable message"}

Validation failures (422) may return structured detail arrays (Pydantic field errors).

Policy and quota responses often add:

Header Meaning
X-Deeplife-Error-Code Machine-readable code (see tables below)
Retry-After Seconds to wait before retry (rate limits)
x-request-id / x-correlation-id Request id for support

HTTP status → SDK exception

HTTP SDK exception Typical cause
401 AuthenticationError Missing, invalid, or expired API key
404 NotFoundError Unknown prediction id or artifact not ready
422 ValidationError Dataset / schema validation on upload
429 See below Quota, in-flight limit, or rate limit
5xx ServerError Server-side failure
Other 4xx ApiError Generic client error
Network / timeout TransportError No HTTP response (httpx transport errors)

All SDK HTTP exceptions inherit from ApiError and expose:

  • message — API detail text
  • status_code — HTTP status when available
  • error_code — from X-Deeplife-Error-Code when present
  • retry_after_seconds — from Retry-After when present
  • user_message — short text safe for notebooks
  • debug_message — full diagnostic string for logs
from deeplife.twincell import NotFoundError, QuotaExceededError, RateLimitError

HTTP 429 variants

X-Deeplife-Error-Code SDK exception user_message (summary)
rate_limited RateLimitError Too many requests; wait and retry
quota_exceeded QuotaExceededError Prediction quota exceeded for this period
inflight_limit InflightLimitError Another prediction already in progress
external_prediction_quota_exceeded QuotaExceededError Maximum prediction requests for this account
external_prediction_quota_disabled QuotaExceededError Prediction requests disabled for this account
(absent on 429) RateLimitError Treated as throttling

Policy X-Deeplife-Error-Code values (non-429)

Code HTTP Meaning
prediction_external_target_validation_only 403 Only single-target predictions are available on your account
prediction_external_split_required 403 Pass control and perturbed data as separate files
prediction_get_job_type_forbidden 403 Job type not exposed for external GET
causal_analysis_internal_only 403 Causal analysis endpoint restricted

These map to ApiError (or ValidationError for 422) with error_code set on the exception.

Two kinds of 403

Not every 403 comes from the API. Check the body and the X-Deeplife-Error-Code header before acting:

Source Body error_code What it means
API policy JSON {"detail": …} one of the codes above Your account or key tier may not do this. The request will never succeed as written — change the request (see Limits & quotas).
Edge / WAF HTML page None The request was blocked before reaching the API. Nothing about your key, account, or payload is wrong.

Because an edge 403 is transient, the SDK retries it on GET-style calls (polling included) using the standard RetryConfig backoff. POST uploads are never retried, and a labelled policy 403 fails immediately — it would never succeed. You therefore only see an edge 403 raised after the retries are exhausted.

At that point there is no JSON to parse, so the SDK raises a bare ApiError with status_code=403, error_code=None, and the generic message "API request failed"; the HTML is preserved on response_text.

from deeplife.twincell import ApiError

try:
    final = client.wait_for_prediction(prediction_id=prediction_id)
except ApiError as exc:
    if exc.status_code == 403 and exc.error_code is None:
        print(exc.debug_message)  # includes x-request-id for support

The observed pattern is an edge 403 on GET polling — the multipart upload POST succeeds and returns a prediction_id, then status polls start returning HTML 403 partway through the run. The prediction itself keeps running server-side; only your view of it is blocked.

If you hit this:

  • Do not treat it as an auth problem. The same key authenticates fine; the request was blocked at the edge before it reached the API.
  • Check your poll rate. The edge allows 30 requests per 60 s per IP, so the SDK's default poll_interval_seconds of 5 s uses about 40% of the budget. If you lowered it, or you have several waiters (or colleagues) sharing one IP, raise it — see Limits & quotas.
  • Re-read the result later with client.get_prediction(prediction_id=…) — the run completes regardless of the blocked polls. Reaching this error means the automatic retries were exhausted, which points at sustained pressure on the IP rather than a brief spike; the default budget (RetryConfig: 3 attempts, up to 8 s apart) covers a short block, not a full 60 s window.
  • Report it with the x-request-id from debug_message and the timestamp via Access & Support.

Failed predictions (status: failed)

wait_for_prediction() and watch_prediction() return a failed status rather than raising; call status.raise_if_failed() if you want a PredictionFailedError. The API body includes:

  • error_message — user-facing text (PredictionFailedError.message)
  • error_code — stable worker code when available

status: failed covers two very different situations, and the error_code is what separates them.

Biological outcomes, not faults

Three worker codes mean TwinCell ran correctly and concluded that no causal result exists for your target and DEGs. Nothing is broken, and re-running the same inputs cannot produce a result:

error_code Billable? Meaning
target_not_in_interactome No Target absent from the interactome, so no path to it can exist
degs_not_in_interactome No None of the submitted DEGs mapped, so there is nothing to trace a path from
target_not_reachable Yes Target is on the interactome, but no causal path reaches it from your DEGs

target_not_reachable is billable because the model did the work to reach that conclusion.

The API reports these as failed and then 404s every result route (mapped-degs, degs-impacted, causal-paths, causal-graph, intermediary-proteins), which at the HTTP layer is indistinguishable from an unknown prediction id. The SDK resolves that ambiguity for you: TwinCell.get_target_score(), get_all_degs(), get_degs_impacted_by_target(), get_causal_paths(), get_intermediary_nodes(), and plot_causal_graph() all raise NoCausalPathError, which states the conclusion in plain language.

from deeplife.twincell import NoCausalPathError

try:
    score = tc.get_target_score(prediction_id=prediction_id)
except NoCausalPathError as outcome:
    print(outcome.reason)       # why no result exists
    print(outcome.error_code)   # e.g. "target_not_reachable"
    print(outcome.target)       # e.g. "TYK2|PROTEIN"

NoCausalPathError is deliberately not an ApiError subclass, so a broad except ApiError around your workflow will not swallow a biological finding as though it were an infrastructure problem. To branch without catching, inspect the status directly:

status = client.get_prediction(prediction_id=prediction_id)
if status.is_biological_outcome:
    print(status.outcome_reason)

Genuine failures

A failed run with no error_code (or an unrecognised one) is a real fault — a worker crash or an internal error. These keep their original behaviour: result routes raise NotFoundError, and get_target_score() raises ValueError. error_detail carries the traceback for internal accounts.

Constants: ERROR_TARGET_NOT_IN_INTERACTOME, ERROR_TARGET_NOT_REACHABLE, ERROR_DEGS_NOT_IN_INTERACTOME, BIOLOGICAL_PREDICTION_ERROR_CODES, NON_BILLABLE_PREDICTION_ERROR_CODES.

Local (non-HTTP) errors

These are raised before or after HTTP and are not in the REST table above:

Exception When
NoCausalPathError The run reached a biological conclusion; no causal result exists (see above)
ValueError Missing target on create_prediction_split; missing session state (no prediction id, unknown target row, etc.)
TypeError Wrong argument types or missing required study inputs
FileNotFoundError Local .h5ad path missing (read_h5ad)
deeplife.twincell.validation.ValidationError Local split AnnData checks failed before upload
botocore.exceptions.ClientError S3 download errors when loading remote h5ad