Developers

Errors

Every error response uses the same envelope, with a stable, machine-readable code and the request_id of the call. The HTTP status tells you the class of problem; code tells you exactly what went wrong. The message text follows Accept-Language (pt-BR, en-US, es-ES, fr-FR, defaulting to en-US); code never changes.

{
  "error": {
    "code": "validation_failed",
    "message": "The request has invalid fields.",
    "request_id": "01J8Z3ZQ4H8K2M0T1S9P7YQF5C",
    "details": [
      { "field": "name", "code": "required", "message": "Name is required." }
    ]
  }
}
  • code is the only field your integration should branch on. Handle any code you do not recognize by its HTTP status.
  • details is always present. It lists the individual problems, typically on 400 and 422, and is an empty array when there is nothing to itemize. Each item carries field (request body field) or parameter (query parameter), code (the rule that was violated), and message. Business rule rejections add reason with the specific cause.
  • request_id matches the X-Request-Id response header and the entry in the audit trail (GET /requests/{request_id}). Include this value when you report a problem.
  • A resource outside the scope of the current credentials returns 404, exactly like a resource that does not exist. The API never confirms the existence of something you cannot see.
  • The API fails closed: if the credential state, its permissions, or the rate limiter cannot be verified, the response is 503, never access.
  • A validation error executes nothing and does not consume the Idempotency-Key. A 429 response does not count against the rate limit window.

Codes

CodeHTTPWhen
unauthenticated401The credential is missing, has an unrecognized format, or does not exist.
credential_revoked401 or 422The credential has been revoked. Credential management operations return 422 instead.
credential_suspended401The credential has been suspended by Cademí.
credential_expired401The credential has expired.
ambiguous_credentials401The call sends an administrator's access token together with a credential secret, or an ID token instead of an access token.
invalid_access_token401The administrator's access token is invalid, expired, or revoked, or its administrator was removed or moved to another account.
instance_blocked403The account is blocked or has been removed.
instance_unavailable403The credential's account is not available to the API.
credential_state_unverifiable503The credential state could not be verified, so the API fails closed.
permission_denied403The caller, a credential or an administrator, lacks the permission required by the operation.
personal_data_permission_required403The credential lacks a permission required to access personal data at the requested level, such as setting a webhook's payload_detail to full.
not_found404The resource does not exist, has been deleted, or is not accessible with the current credentials.
operation_not_catalogued503The route is not a recognized public operation, so the request is rejected as a safety measure.
not_acceptable406The request's Accept header does not allow application/json.
unsupported_media_type415The Content-Type is not application/json.
payload_too_large413The request body exceeds 1 MiB.
malformed_json400The request body is not valid JSON.
json_too_deep400The JSON is nested more than 16 levels deep.
invalid_utf8400The request body contains bytes that are not valid UTF-8.
validation_failed422A required field is missing or a value was rejected. details lists each field and rule.
unknown_field422The request body contains a field that is not in the schema.
unknown_parameter400The query string contains a parameter that is not in the schema.
invalid_cursor400The cursor was altered, belongs to a different sort order, or has expired (24 hours).
method_not_allowed405The HTTP method is not supported by the route.
operation_sunset410The operation was called after its announced Sunset date. See Versioning and compatibility.
revision_mismatch412The If-Match value does not match the current revision of the resource.
if_match_invalid400The If-Match header is neither * nor a value in the ETag format.
invalid_transition422The requested state change is not available for the resource.
credential_not_removable422The credential has usage history and can only be revoked.
score_not_removable422The point entry was earned automatically (lessons, courses, certificates, questions, comments, or exams) and cannot be deleted. DELETE /users/{user_id}/scores/{score_id} deletes only manual entries.
delegation_limit_exceeded403The request tried to grant more than the caller has: a full-access administrator grants anything; any other administrator grants only what their own permissions grant; a credential grants another credential or an administrator only the delegable permissions of its own policies, never administrators.* or credentials.*. See Permissions and scope.
idempotency_key_required422A state-changing POST request was sent without an Idempotency-Key.
idempotency_key_invalid422The Idempotency-Key is malformed (it must be 1 to 128 characters).
idempotency_key_reused422The same key was already used with a different body or route.
idempotency_in_progress409A request with the same key is still being processed.
result_uncertain409The first attempt did not reach a recorded result. Retry the call.
secret_not_replayable409A replay does not return a secret that is shown only once.
result_not_replayable409The recorded result cannot be replayed for this call.
operation_type_unknown422The batch type is not a supported operation type.
duplicate_item_key422Two items in the batch share the same key.
operation_backlog_exceeded422The account already has too many open operations.
operation_not_cancelable422The operation has already finished.
operation_not_resumable422The operation has not failed or has no recoverable items.
item_invalid422The payload of a batch item does not have the shape its type requires.
too_many_items422An array exceeds the maximum size. For operation batches, the limit depends on the operation type (up to 1,000 items).
operation_ownership_lost409Another attempt took over the operation, and this attempt was discarded.
domain_rule_violation422A business rule was violated that has no dedicated public code.
http_errorSame as the responseGeneric HTTP error. The response status is the status of the failure.
internal_error500An unexpected failure occurred. No further detail is provided.
audit_unavailable503The audit trail could not be recorded, so the call is not confirmed.
rate_limit_exceeded429The per-minute limit for the source, the account, or the caller (credential or administrator) was reached.
rate_limiter_unavailable503The rate limiter did not respond, so the API fails closed.
state_conflict409The resource is in a state that does not allow this call. Also returned, with details[].reason set to self_modification, when an administrator tries to change their own permissions.
progress_not_started409A progress reset was requested for a user who has never opened the product, so there is no progress to reset.
upload_integrity_mismatch422The uploaded bytes do not match the declared size, checksum, or type.
storage_unavailable503The file storage service did not respond.
quota_exceeded409The upload would take the account's storage in use above its quota. details[0] carries reason, used_bytes, and quota_bytes. See Files and uploads.
import_file_unreadable422The import spreadsheet could not be read. See Imports, exports, and reports.
import_file_empty422The import spreadsheet has no data rows below the header.
import_column_missing422A required column is missing from the import spreadsheet. reason reports the missing column.
sandbox_only403The operation is available only in sandbox accounts, for example calling POST /sandbox/resets with production credentials. Make the request with a sandbox credential. Handle it the same way as sandbox_required.
replica_readonly403The record is a replica copied from another account and is read-only, except for status. Other edits, publishing, and deletion are rejected.
order_set_mismatch422The reorder request does not contain exactly the set of IDs in scope.
hierarchy_violation422The target would break the hierarchy (a parent inside its own child, or an invalid level).
banner_type_unavailable422The banner type is not available for the account.
already_exists409A resource with the supplied identifier (external_id, slug) already exists.
plan_limit_reached409The account's plan does not allow another resource of this type.
email_recently_sent409The same email was sent recently. Wait for the window to pass.
feature_disabled409The feature is turned off in the account settings. Also returned, with details[].reason set to first_import_requires_release, when processing the first import of an account that the Cademí team has not released yet; contact support to release it.
tag_in_use409The tag is in use and cannot be deleted.
template_not_previewable409The template has nothing to preview.
format_unsupported409The requested format is not supported by the resource.
too_many_streams429The limit of event streams per credential (5) or per account (20) was reached.
stream_busy409The event stream already has an open connection.
cursor_expired410The cursor points to an event that is no longer retained (30-day retention).
sandbox_required409The test scenario runs only in a sandbox account, for example calling POST /sandbox/test-scenarios/{scenario_id}/runs with production credentials. Make the request with a sandbox credential. Handle it the same way as sandbox_only.
unsupported_resource422The resource type is not accepted by this operation.
plan_invalid422The declarative manifest failed validation.
plan_stale409The plan was computed against a state that has since changed.
plan_expired409The plan has expired and must be computed again.

What to do for each class

StatusAction
400, 404, 405, 406, 409, 412, 413, 415, 422Client error. Retrying without changes produces the same result.
401, 403Credential or permissions. Review the credential and its permissions before retrying.
429Wait for retry_after_seconds (or the Retry-After header) and retry. See Limits, quotas, and retention.
500, 503Server failure or unavailability. Retry with exponential backoff; for state-changing requests, use the same Idempotency-Key.

If a state-changing request returns 500, 503, or times out, the outcome is uncertain: retry with the same Idempotency-Key. If the first attempt was recorded, the retry returns the same response with Idempotent-Replayed: true and creates nothing new.

Report a problem

If the API behaves differently from what this documentation describes, open an API bug report. Include the operation, the request_id of the call, the account environment (production or sandbox), the request and the response without secrets, and the release from the X-Cademi-Release header. With the CLI, cademi bug --api --request-id <id> opens the same report already filled in. See Report a problem in the CLI guides.

  • Questions about using the API: Discussions.
  • Security vulnerabilities, including webhooks, OAuth, and event streams: report them privately at github.com/minhacademi/developers/security, never in an issue.
  • Questions about your account, plan, or data: contact Cademí support.

Issues and discussions are public. Never paste an API key, a token, or data about your users.

On this page