Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Errors and error codes

When something goes wrong, the Python client raises an exception and the R client stops with an error condition. Errors that come from the CyteType server carry a machine-readable code, such as RATE_LIMIT_EXCEEDED, and a message that explains the problem. This page lists the exceptions, the codes and what to do about each one. For problems described by their symptoms, see Troubleshooting.

Python exceptions

All CyteType exceptions can be imported from cytetype (and from cytetype.api); the Python API reference documents each class:

from cytetype import (
    CyteTypeError,
    APIError,
    AuthenticationError,
    AuthorizationError,
    RateLimitError,
    QuotaExceededError,
    JobNotFoundError,
    JobFailedError,
    LLMValidationError,
    ClientDisconnectedError,
    NetworkError,
    CyteTypeTimeoutError,
)

They form this hierarchy:

CyteTypeError                      base class: .message, .error_code
├── APIError                       errors reported by the server (any code not listed below)
│   ├── AuthenticationError        INVALID_TOKEN, TOKEN_INACTIVE, AUTHENTICATION_REQUIRED
│   ├── AuthorizationError         ACCESS_DENIED, LICENSE_EXPIRED, VPN_BLOCKED
│   ├── RateLimitError             RATE_LIMIT_EXCEEDED
│   ├── QuotaExceededError         QUOTA_EXCEEDED
│   ├── JobNotFoundError           JOB_NOT_FOUND
│   ├── JobFailedError             JOB_FAILED, or the job's status became "failed"
│   ├── LLMValidationError         LLM_VALIDATION_FAILED
│   └── ClientDisconnectedError    CLIENT_DISCONNECTED
├── NetworkError                   the server could not be reached, or its reply could not be read
└── TimeoutError                   a request, or the wait for a job, took too long
                                   (import it as CyteTypeTimeoutError)

Every CyteType exception has two attributes:

A few points to keep in mind:

Errors raised before anything is sent

Problems with your data or arguments are found on your computer and raise standard Python exceptions:

ExceptionRaised byTypical causes
KeyErrorCyteType()group_key is not a column of adata.obs; the rank_genes_groups results are not in adata.uns under rank_key; gene_symbols_column is not a column of adata.var.
ValueErrorCyteType()Missing cluster labels (NaN) without label_na=True; gene IDs instead of gene symbols (“Gene Symbol Detection Error”); rank_genes_groups computed with another groupby; marker genes that do not match adata.var_names; an invalid api_url.
ValueErrorrun()Results with the same results_prefix already exist; visibility is not "private", "org", "public" or None; upload_timeout_seconds is not positive; cleanup() deleted the files and require_artifacts=True; an invalid api_url.
pydantic.ValidationErrorrun()metadata values that are not strings; an llm_configs entry with an unknown provider or without apiKey (or the AWS fields); n_parallel_clusters outside 1 to 50; a study_context that is not a string.

pydantic.ValidationError is a subclass of ValueError, so except ValueError catches it too. These checks run before any file is uploaded. For the fixes, see Troubleshooting.

With require_artifacts=True (the default), a failure while building or uploading vars.h5 or obs.duckdb re-raises the original exception, for example an OSError when the disk is full or a ValueError when a file is larger than the upload limit. See Artifacts and uploads.

When each error can occur in run()

run() works through these steps in order, so the step tells you how far a job got:

  1. Checks on the arguments and the CyteType object: ValueError.

  2. Finding your API key: AuthenticationError with AUTHENTICATION_REQUIRED if no key is saved for the server and none was passed. With visibility="org" and a key that looks like a personal key (cyt_p_...), run() then logs a warning; only the server can refuse the key, at step 5.

  3. Validating the request: pydantic.ValidationError.

  4. Uploading vars.h5 and obs.duckdb: a rejected key raises AuthenticationError and a refused account (for example an expired license) raises AuthorizationError, even with require_artifacts=False. Other upload failures raise only with require_artifacts=True.

  5. Submitting the job: RateLimitError, QuotaExceededError, LLMValidationError, or APIError (for example “Org visibility requires an organization license” when visibility="org" is used with a key that has no organization license, such as a personal API key). All job limits and quotas are checked here, after the upload, so fixing these errors means uploading again.

  6. Waiting for the job: JobFailedError if the job fails, CyteTypeTimeoutError if it does not finish within timeout_seconds. Brief network problems while waiting are retried. Once the job has been submitted, its ID is stored in adata.uns and you can fetch the results later with get_results().

Server error codes

The table summarizes the codes; the sections below give the typical causes and fixes.

CodePython exceptionIn short
INVALID_TOKENAuthenticationErrorThe API key is not recognized.
TOKEN_INACTIVEAuthenticationErrorThe API key was disabled.
AUTHENTICATION_REQUIREDAuthenticationErrorNo API key was available.
ACCESS_DENIEDAuthorizationErrorYou may not access this job or resource.
LICENSE_EXPIREDAuthorizationErrorYour organization’s license has expired.
VPN_BLOCKEDAuthorizationErrorThe request was refused because of the network it came from.
RATE_LIMIT_EXCEEDEDRateLimitErrorYour personal account reached a job limit.
QUOTA_EXCEEDEDQuotaExceededErrorNot enough clusters left in the quota.
JOB_NOT_FOUNDAPIError or JobNotFoundErrorNo job with this ID on this server.
JOB_FAILEDJobFailedErrorThe job failed on the server.
LLM_VALIDATION_FAILEDLLMValidationErrorA model in llm_configs failed the check before the job started.
CLIENT_DISCONNECTEDClientDisconnectedErrorThe connection closed before the server had the whole request.
Other codesAPIErrorSee Other codes.

INVALID_TOKEN

The server does not recognize the API key: “Invalid API token”.

TOKEN_INACTIVE

The key exists but has been disabled: “API token has expired or been deactivated”.

AUTHENTICATION_REQUIRED

No API key was available.

ACCESS_DENIED

You may not access this job or resource: “You do not have permission to access this job”.

LICENSE_EXPIRED

The organization license that the key belongs to has expired: “License has expired. Please contact your organization admin(s): ...”.

VPN_BLOCKED

The server refused the request because of the network it came from.

RATE_LIMIT_EXCEEDED

Your personal account reached one of its job limits. The message names the limit and when it resets, for example “Daily limit reached (10/10). Resets in about 5 hours.”

QUOTA_EXCEEDED

The job has more clusters than remain in the quota: “Quota exceeded: This job requires 24 clusters but you only have 10 remaining (990/1000 used)”.

JOB_NOT_FOUND

The server has no job with this ID: “Job ... not found” (Python: “Results not found for job ...”).

JOB_FAILED

The job failed on the server. In Python, run() raises JobFailedError with “Job ... failed” as soon as the job’s status becomes failed, and prints the report link.

LLM_VALIDATION_FAILED

A model in llm_configs did not pass the check that runs before the job starts: “All provided models failed validation: ... Ensure the model is reachable and supports tool calling.”

CLIENT_DISCONNECTED

The connection closed before the server had received the whole request: “Client closed the connection before the request completed”.

Other codes

These codes raise a plain APIError (or NetworkError, where noted):

CodeMeaningWhat to do
INTERNAL_ERRORAn unexpected error on the server (“An unexpected error occurred: ...”).Try again later. The Python client retries each part of a file upload up to three times on its own. If the error persists, contact support with the message.
HTTP_ERRORA request was refused for another reason; the message says why, for example “Org visibility requires an organization license”. It is a NetworkError when the reply could not be read at all, for example an error page from a proxy.Act on the message.
REPORT_DELETEDThe owner deleted the report (“This report has been deleted by the owner”).Deletion cannot be undone. Ask the owner if they have another copy of the results.
JOB_PROCESSING, JOB_NOT_COMPLETEDThe job has not finished yet.Wait and fetch the results again.
INVALID_RESPONSESet by the client: the server’s reply was not in the expected form.Try again; if it persists, update the client and contact support.
PRESIGNED_URL_REJECTED, MISSING_ETAGSet by the client: the file storage service rejected part of an upload (MISSING_ETAG is a NetworkError).Run again. If it keeps happening on a slow connection, see Artifacts and uploads.

Catch and handle errors in Python

Catch the specific classes you can act on, then CyteTypeError for the rest:

from cytetype import (
    AuthenticationError,
    CyteType,
    CyteTypeError,
    CyteTypeTimeoutError,
    JobFailedError,
    LLMValidationError,
    QuotaExceededError,
    RateLimitError,
)

annotator = CyteType(adata, group_key="leiden", rank_key="rank_genes_leiden")

try:
    adata = annotator.run(study_context="Human PBMC from a healthy donor")
except AuthenticationError as err:
    print(f"API key problem ({err.error_code}): {err.message}")
    print("Run `cytetype setup --force`, or pass a valid auth_token.")
except RateLimitError as err:
    print(f"Job limit reached: {err.message}")
except QuotaExceededError as err:
    print(f"Quota exceeded: {err.message}")
except LLMValidationError as err:
    print(f"A model in llm_configs failed validation: {err.message}")
except JobFailedError as err:
    print(f"The job failed on the server: {err.message}")
except CyteTypeTimeoutError as err:
    # A submitted job keeps running on the server.
    print(f"Timed out: {err.message}")
    print("Fetch the results later with annotator.get_results().")
except CyteTypeError as err:
    print(f"CyteType error ({err.error_code}): {err.message}")
    raise

run() returns the annotated AnnData object, so assign its return value (here back to adata). With label_na=True, it can be a new object.

To retry automatically after a rate limit, read the reset time from the message and run again later. Do not retry AuthenticationError, AuthorizationError or QuotaExceededError in a loop: they need a change on your side.

R errors

CyteTypeR signals errors in these classes:

error
├── cytetype_api_error         a problem with the CyteType API: e$message, e$error_code, e$call ("network" or "api")
│   └── cytetype_auth_error    sign-in and API key problems: e$message, e$error_code
├── cytetype_timeout_error     CyteTypeR() stopped waiting at timeout_seconds: e$message, e$job_id
└── simple R errors            everything else, including most refusals from the server

cytetype_auth_error conditions (which also inherit cytetype_api_error) carry an error_code:

error_codeWhen
AUTHENTICATION_REQUIREDNo key is saved for the server (“CyteType sign-in is required. Run SetupCyteTypeR() first.”), the credentials file is damaged (“Invalid CyteType credentials file: ... Run LogoutCyteTypeR(), then SetupCyteTypeR().”), or GetResults() was given auth_token for a job stored in the object without api_url.
API_ORIGIN_MISMATCHGetResults() was given an api_url that differs from the server stored with the job.
INVALID_TOKEN, TOKEN_INACTIVE, LICENSE_EXPIREDSetupCyteTypeR() or LoginCyteTypeR() was refused by the server. INVALID_TOKEN is also used when a request for a job’s results is refused with “Authentication failed: Invalid or expired auth token”.
ACCESS_DENIEDThe server refused to return a job’s results (“Authorization failed: Access denied”): the key is unknown or disabled, or the job is neither yours, nor an organization report of the key’s organization, nor public. Also used when you select Cancel on the authorization page during setup.
TIMEOUT, NETWORK_ERROR, CALLBACK_SERVER_ERROR, INVALID_RESPONSE, AUTHENTICATION_FAILEDBrowser sign-in or key validation failed: it was not finished within 5 minutes, the server could not be reached, the local listener could not start, the server’s reply was not as expected, or another refusal.

cytetype_timeout_error means that CyteTypeR() stopped waiting after timeout_seconds (default 7200). The job keeps running on the server. e$job_id holds its ID, and the message names the call that fetches its results: “Job <job_id> did not finish within 7200 seconds (timeout_seconds). The job keeps running on the server; retrieve its results later with GetResults(job_id = “<job_id>”).”

cytetype_api_error is the parent class of cytetype_auth_error, so a handler for it also catches API key problems. CyteTypeR signals it when a job’s status or results cannot be fetched: by GetResults(), and by CyteTypeR() while it waits (after five consecutive errors, or when a completed job’s results cannot be fetched or are not in the expected form). e$error_code holds the server’s code when it sent one, such as REPORT_DELETED or RATE_LIMIT_EXCEEDED. An uncaught one prints as Error: <message>; print(e) shows CyteType API Error: <message>.

Simple R errors cover the rest. When the server refuses an upload or a job submission, the message names the step and gives the server’s explanation, with its code in brackets when the server sent one:

Upload initiate failed: [TOKEN_INACTIVE] API token has expired or been deactivated
Job submission failed: [QUOTA_EXCEEDED] Quota exceeded: This job requires 24 clusters but you only have 10 remaining (990/1000 used)
Job submission failed: [LLM_VALIDATION_FAILED] All provided models failed validation: ...

Other messages you may see:

Catch and handle errors in R

Handle cytetype_auth_error, cytetype_timeout_error and cytetype_api_error first (their fields name the problem), then match server codes in the message of other errors:

annotated <- tryCatch(
  CyteTypeR(
    obj = pbmc,
    prepped_data = prepped_data,
    study_context = "Human PBMC from a healthy donor"
  ),
  cytetype_auth_error = function(e) {
    message("API key problem (", e$error_code, "): ", conditionMessage(e))
    message("Run SetupCyteTypeR(force = TRUE) or LoginCyteTypeR(), then try again.")
    NULL
  },
  cytetype_timeout_error = function(e) {
    # The job keeps running on the server.
    message("Timed out: ", conditionMessage(e))
    message("Fetch the results later with GetResults(job_id = \"", e$job_id, "\").")
    NULL
  },
  cytetype_api_error = function(e) {
    # The job's status or results could not be fetched; the job may still be running.
    code <- if (is.null(e$error_code)) "no code" else e$error_code
    message("CyteType API error (", code, "): ", conditionMessage(e))
    NULL
  },
  error = function(e) {
    msg <- conditionMessage(e)
    if (grepl("INVALID_TOKEN|TOKEN_INACTIVE", msg)) {
      message("The API key was rejected. Run SetupCyteTypeR(force = TRUE).")
    } else if (grepl("limit reached", msg, fixed = TRUE)) {
      message("Job limit reached: ", msg)
    } else if (grepl("QUOTA_EXCEEDED", msg, fixed = TRUE)) {
      message("Quota exceeded: ", msg)
    } else {
      stop(e)
    }
    NULL
  }
)

if (!is.null(annotated)) pbmc <- annotated

CyteTypeR() returns the annotated Seurat object, so keep its return value. If it stops with an error, your original object is unchanged.