Errors

Every Koolbase error speaks one shape: a human-readable message plus a stable, machine-readable code. Branch on the code — never on the message text.

The error shape

Any non-2xx response is a flat JSON object with two fields, plus an optional details:

{
  "error": "invalid email or password",
  "code": "invalid_credentials"
}
  • error — a human-readable message, for logs and quick debugging. Wording can change between releases; don't parse it.
  • code — a stable, snake_case identifier you can switch on. This is the contract.
  • details — present on a few codes that carry structured context (see below).

Code over message

The code is the part we keep stable across releases. Matching on the error string is brittle — a copy-edit to a message would silently break your handling. Switch on code.

Structured details

Some codes attach a details object. A unique_violation names the field that collided, so you can point the user at the right input:

{
  "error": "value violates a unique constraint",
  "code": "unique_violation",
  "details": { "field": "email" }
}

When you declare a unique constraint on a collection that already holds duplicates, the rejection carries details.duplicates — the offending value groups, so you can dedupe before retrying.

Error codes

Every code is global and stable. The defaults below apply to any endpoint based on the HTTP status; specific endpoints emit the more precise codes that follow.

Four codes are deliberately absent from these tables — error, invalid, duplicate and state_conflict. They carry nothing an application can act on, and a more specific code applies wherever one exists. Treat them as you would an unrecognised code.

Defaults

CodeHTTPMeaning
unauthenticated401Missing or invalid authentication (no/expired token).
permission_denied403Authenticated, but not allowed to perform this operation.
not_found404The requested resource does not exist.
conflict409Generic conflict with the current state.
rate_limit429Too many requests — back off and retry.
validation_error400The request was malformed or failed validation.
internal_error5xxAn unexpected server error.

Database

CodeHTTPMeaning
unique_violation409A write violates a unique constraint. Carries details.field.
collection_not_found404No collection with that name.
record_not_found404No record with that id.
ambiguous_match409An upsert matched more than one record.
duplicate_values409Declaring a constraint, but existing rows already violate it. Carries details.duplicates.
constraint_exists409A unique constraint already exists for those fields.
constraint_not_found404No such unique constraint.
reference_invalid400A reference field points at a record that does not exist, is deleted, or is in another collection. Checked when the write commits, so it can come from an insert, an update or a batch — a batch is refused whole.
reference_in_use409Deleting a record that live records still reference, under a reference declared on_delete: restrict. Delete the referencing records first, or in the same batch — the check runs at commit, so one batch may delete a parent and its children in any order.
dangling_references409Declaring a reference over data that already points at missing records. details.dangling lists the offending records (first 50) so you can repair them; Koolbase never repairs them for you.
collection_referenced409Deleting a collection another collection's reference field points at. Remove that reference first.
revision_mismatch409The record moved between composing the change and the write arriving. Carries both revisions and the current record, so the conflict can be resolved without fetching again.
idempotency_key_reused409The same idempotency key sent with different data — refused rather than replayed.
idempotency_conflict409The fiscal counterpart of idempotency_key_reused: a client_ref that exists with a different payload.
batch_failed400A batch write failed for a reason the server did not classify further.
insufficient_authority403Not permitted to perform a destructive operation such as a seed overwrite or snapshot restore. Unlike insufficient_scope, this is about the operation rather than the key.
invalid_body400The request body could not be decoded.
no_changes400The request asks for no change.

Vectors & embeddings

CodeHTTPMeaning
vector_dimension_mismatch400The vector's dimension does not match the field declaration — usually a changed model, or the wrong model producing the embedding.
vector_not_found404No vector on that record for that field.
vector_field_not_found404The collection has no vector field with that name.
vector_field_exists409A vector field with that name already exists on the collection.
vector_collection_mismatch400The record's collection does not match the vector field's collection.
unsupported_dimension400That vector dimension is not supported.
invalid_embedding_config400provider, model and source_field must be set together or all cleared — partial config is refused rather than half-applied.
field_not_auto_embed400A backfill was asked for on a field that embeds nothing automatically.
provider_not_configured400No embedding provider is configured; embedding runs on the project's own provider key.
provider_invalid400The embedding provider's credentials were rejected.

Authentication

CodeHTTPMeaning
invalid_credentials401Wrong email or password.
email_in_use409That email is already registered.
email_not_verified403Email must be verified before sign-in.
account_locked429Too many failed attempts; temporarily locked. Honour the Retry-After header.
account_disabled403The account has been disabled.
invalid_refresh_token401Refresh token rejected — the session is over; sign in again.
invalid_unlock_token400The unlock-email token is invalid, expired, or already used.
invalid_token400A verification or password-reset token is invalid or expired.
token_used400That token has already been used.
token_expired400That token has expired — request a new one.
invitation_invalid400The invitation has been revoked or expired.
oauth_not_configured400Apple/Google sign-in is not configured for this environment.
invalid_oauth_token401The Apple/Google identity token failed verification.
oauth_email_required400The provider did not return an email for this sign-in.
oauth_email_conflict409The email belongs to an existing account; sign in and link from settings.
invalid_phone400Phone number is not valid E.164.
otp_expired400The OTP has expired.
otp_invalid400The OTP code is incorrect.
otp_max_attempts400Too many incorrect OTP attempts.
phone_in_use409That phone number is linked to another account.
sms_not_configured400No SMS provider is configured for this project.
weak_password400Password must be at least 8 characters — the server's rule, which a project may set higher than the SDK checks.
invalid_password400A password change was refused because the CURRENT password is wrong.
signups_disabled403New registrations are off for this project.
email_code_disabled403The project has switched off signing in with an emailed code. Applies to every address alike.
account_exists409That email already has an account — sign in with its existing method, then connect this provider.
contact_not_verified403Email or phone must be verified before sign-in, because the project requires verified contact.
last_credential409Cannot remove the last sign-in method — the account would have no way in.
resend_cooldown429Another verification email was asked for too soon. Carries cooldown_until.
resend_daily_cap429The daily verification-email limit is reached.
hide_requires_verification400hide_account_existence needs require_verified_contact enabled.
oauth_only_account403This account signs in with Google or Apple and has no password.
unsupported_oauth_provider401That sign-in method is not available.
identity_not_found404That provider is not connected to this account.
provider_identity_already_linked409Another account already holds that Google or Apple identity.

Storage

CodeHTTPMeaning
path_conflict409An object already exists at this bucket+path and overwrite was false. Uploads are safe-by-default; pass overwrite: true on the upload-url request to replace.
quota_exceeded409Upload would push the bucket past its configured max_size_bytes.
file_too_large413Single file exceeds the bucket's configured max_file_size_bytes.
mime_not_allowed415File content_type is not in the bucket's allowed_mime_types list. Wildcards like image/* match the family; an empty allowed list means no restriction.
upload_expired410Confirm was called after the upload grace window (~1 hour) and the presigned URL is too old to honor. Request a new upload URL and try again.
metadata_invalid400Metadata failed validation. A top-level detail names the failing key and the rule it broke — read it there, not under details.
cap_below_usage400A bucket cap set below what the bucket already holds; the message carries both figures.
upload_url_failed400Minting a presigned upload URL for a template asset failed. Templates rather than object storage, despite the name.

Platform & billing

CodeHTTPMeaning
plan_limit_reached402The operation would exceed your plan's limit. Carries details: { resource, limit, plan }. Existing resources are never affected — only the new create or new usage is refused.
insufficient_scope403The API key's scope is below what this operation requires (read < write < admin). The message names the required scope.
session_required403This operation requires a dashboard session — API keys cannot perform it (e.g. minting new API keys).

Projects & seeding

CodeHTTPMeaning
project_invalid400Invalid project id.
slug_taken409A project with this slug already exists.
invalid_seed_file400The seed file is not valid; details carry a problems list.
seed_key_not_unique400The seed file's key does not identify rows uniquely; the message names the problem.
seed_needs_decision409The seed cannot proceed until the caller chooses how to handle conflicts.
seed_conflicts_require_force409Rows changed in both the seed file and the target; overwriting discards the target's version. details carry the rows.

Handling errors in the SDK

The SDKs do the code matching for you — they throw typed exceptions selected from the code, so your app catches a class instead of inspecting status or message. Data-layer failures share a base (KoolbaseDataException) you can catch broadly, or you can catch a specific subtype:

lib/database.dart
Dart
try {
  await Koolbase.db.collection('users').insert({'email': email});
} on KoolbaseConflictException catch (e) {
  // e.field is the collided field, when the server reports it
  showError('That ${e.field ?? 'value'} is already taken.');
} on KoolbasePermissionException {
  showError('You do not have permission to do that.');
} on KoolbaseDataException catch (e) {
  // Catch-all for any other data-layer error
  showError(e.message);
}

Auth methods throw KoolbaseAuthException subtypes — InvalidCredentialsException, AccountLockedException, OtpExpiredException, and so on — selected from the same codes.

Storage uploads have their own typed exception family — KoolbaseStorageConflictException, KoolbaseStorageQuotaException, KoolbaseStorageFileTooLargeException, KoolbaseStorageMimeTypeException, KoolbaseStorageNotFoundException, KoolbaseStoragePermissionException, KoolbaseStorageValidationException — all extending KoolbaseStorageException. Catch the base to handle any storage failure, or branch on the specific subclass. The upload_expired code surfaces on the base class without a dedicated subclass; check .code if you need to distinguish it.

React Native

The React Native SDK mirrors this with Error-based classes — KoolbaseDataError (base), KoolbaseConflictError (with .field), KoolbaseNotFoundError, KoolbaseValidationError, KoolbasePermissionError, KoolbaseRateLimitError, and KoolbaseAuthError subtypes. Storage gets KoolbaseStorageError (base), KoolbaseStorageConflictError, KoolbaseStorageQuotaError, KoolbaseStorageFileTooLargeError, KoolbaseStorageMimeTypeError, KoolbaseStorageNotFoundError, KoolbaseStoragePermissionError, KoolbaseStorageValidationError. Catch with instanceof. The optimistic write paths (insert, update, delete) sync in the background, so their conflicts surface through the sync engine rather than as a thrown error; query, get, upsert, and deleteWhere throw the typed errors directly.