SmileLineDocs

Errors

Every error uses one JSON envelope, with a machine-readable code where it helps.

All errors return the same shape:

{
	"error": "Human-readable message",
	"code": "OPTIONAL_MACHINE_CODE",
	"details": { "optional": "structured context" }
}

error is always present. code and details appear when there is something structured to say.

Status codes

StatusMeaning
400The request body, query, or params failed validation.
401Missing or invalid credentials.
403Authenticated, but not allowed — no active organization, or your role lacks the required permission.
404The resource doesn't exist, is archived, or belongs to another organization.
409Conflict with current state — for example archiving a stage that still has active journeys, or sending an attachment that expired or was already used.
410A retained capability or artifact is no longer available, such as an expired CSV export (EXPIRED).
413The request body exceeded its byte ceiling before parsing, including public JSON and authenticated multipart uploads.
429Rate limit or pending-upload quota hit. For request rate limits, see Rate limits; the Retry-After header says how long to wait.
500Something broke on our side. The body is { "error": "Internal server error" } with no details.

Operational error monitoring

Authenticated platform errors are reported to SmileLine's EU-hosted operational monitoring so the team can diagnose failures. Server reports contain a sanitised error class, route family, status and stack location; they do not include request bodies, query strings, cookies, authorization headers or raw error messages. Browser diagnostics and session replay travel through the regional SmileLine /ingest proxy. Inputs and rendered text are masked, URL queries and fragments are removed, record identifiers in paths are redacted, and request or response bodies are not recorded.

Validation errors

Failed validation returns 400 with code: "VALIDATION_ERROR" and the individual issues in details:

{
	"error": "Validation failed",
	"code": "VALIDATION_ERROR",
	"details": [
		{
			"path": ["email"],
			"code": "invalid_format",
			"message": "Invalid email address"
		}
	]
}

Each issue has a path into the offending field, a machine code, and a message. Fix the listed fields and retry — validation errors are never transient.

Conversation attachment uploads return a private, one-time upload ID rather than a storage key. Sending an expired, already-used, foreign or duplicate ID returns 409 with UPLOAD_UNAVAILABLE. Abandoned uploads expire after one hour. Exceeding the pending-upload quota returns 429 with UPLOAD_QUOTA_REACHED.

Patient-bearing message metadata has a 1 MiB ceiling. An authenticated send over that boundary returns 413 with MESSAGE_CONTENT_METADATA_TOO_LARGE. Provider-originated inbound mail is bounded and safely omits oversized content metadata instead of asking the provider to retry an impossible row. Retrying a failed message whose content has already been removed by retention returns 409 with MESSAGE_CONTENT_REDACTED.

Some failed messages cannot be retried because sending them now would be wrong rather than merely late. A reminder whose appointment has already started returns 409 with REMINDER_APPOINTMENT_ELAPSED_RETRY_UNSAFE. An ordinary failed message is never refused for its age or for how many times it has been retried: each retry starts a fresh delivery attempt with its own 48-hour horizon. An interrupted reminder whose PMS source fact needs review returns REMINDER_SOURCE_DISPUTED_RETRY_UNSAFE; resolve any pending appointment correction under Settings → Integrations rather than retrying its frozen body. The reminder automation decides whether the resolved appointment is still relevant and safe to re-arm. REMINDER_PMS_BACKFILL_RETRY_UNSAFE means a historical PMS import resolved the source dispute without sending that frozen occurrence. Wait for a later live appointment change to create current reminder content; do not retry the old message. DELIVERY_UNKNOWN_RETRY_UNSAFE means the provider may already have accepted the exact message. A signed callback may still confirm its outcome, but a manual resend could duplicate it. MESSAGE_RETRY_STATE_INVALID means the message's stored delivery record is not readable as a delivery attempt. It is not a limit — send a new message and report it, because a valid record can always be retried.

Live booking availability returns 503 with PMS_AVAILABILITY_RETRY when the practice's PMS diary could not be read in full. SmileLine returns this rather than a partial list of times, so a bookable appointment is never hidden behind an answer that looks complete; retry the request.

Creating or re-enabling a reminder rule returns REMINDER_RULE_LIMIT_REACHED when the practice already has 20 active reminder rules. Switch off another rule, or save the new rule switched off.

PMS appointment-correction commands return PMS_CORRECTION_ALREADY_RESOLVED when the requested decision conflicts with a decision already recorded. Reload the correction list before acting again. PMS_CORRECTION_REMINDER_LIMIT means the practice has more reminder effects than one correction command is allowed to release; reduce or disable reminder rules before retrying. The ordinary VERSION_CONFLICT response means the appointment changed after the review sheet loaded and must be reviewed again.

Practice settings return AUTOMATION_LOCATION_UNAVAILABLE when a selected ID is missing, archived or belongs to another practice. Clearing the selection is allowed and pauses business-hours waits. A PMS booking that has neither a patient location nor a configured automation location returns AUTOMATION_LOCATION_REQUIRED. Archiving the selected automation location returns AUTOMATION_LOCATION_IN_USE.

A deposit payment-page timeout returns CHECKOUT_RECONCILIATION_PENDING rather than minting another payable Stripe session. Retrying is safe and keeps the same provider idempotency generation until Stripe positively reports expiry.

Stripe Connect returns STRIPE_CONNECT_SWITCH_PENDING when a different account is already awaiting confirmation, and STRIPE_CONNECT_SWITCH_SUPERSEDED when a confirm or cancel action lost its exact candidate fence. Reload Settings → Payments & deposits before acting again. OAuth state failures are deliberately generic and cannot be retried; start a new authorization from the settings page.

Optimistic updates return 409 with the single code VERSION_CONFLICT. Reload the latest resource, reapply the intended change, and send its new version.

Subscription Checkout returns CHECKOUT_SESSION_EXPIRED when the durable command points to an expired Stripe session. The browser then starts a new command on the next attempt. CHECKOUT_ALREADY_COMPLETED means the original session completed and the exact command, customer and subscription projection were durably correlated; it must not be recreated. BILLING_CHECKOUT_RECONCILIATION_PENDING means Stripe reports that the session completed but a newer billing generation won while SmileLine was proving that correlation. Retry the same command while its fixed one-hour ambiguity window remains open. SmileLine does not create another Checkout; unresolved state moves to manual review when the window ends.

Webmaster email sends to an address that has never been confirmed are not errors: they return 202 with status: "awaiting_confirmation", no delivery, confirmationState set to sent or cooldown, and the next retryAt. Nothing is rendered or queued until the address confirms.

SEO_WEBMASTER_CONFIRMATION_SEND_FAILED is 503 when the provider does not accept a confirmation request. The reserved attempt is still consumed and the Retry-After header gives the remaining cooling-off period. SEO_WEBMASTER_CONFIRMATION_EXHAUSTED is 409 after all three lifetime confirmation attempts have been used. Contacts cannot be deleted or reset. SEO_WEBMASTER_CONTACT_SUPPRESSED is 409 for a generic permanent recipient or mail-server block. The API does not disclose which provider signal caused it. SEO_WEBMASTER_DAILY_CAP_REACHED and SEO_WEBMASTER_NEW_CONTACT_CAP_REACHED are 429 and reset at midnight UTC. SEO_WEBMASTER_CONTACT_LIMIT_REACHED is 409 when the practice already holds the maximum of 50 permanent webmaster contacts; unconfirmed and blocked contacts still count. A rendered delivery over its per-kind ceiling — 1 MiB for content, 64 KiB for a developer brief — returns 413 with SEO_DELIVERY_PAYLOAD_TOO_LARGE. Reusing a clientRef for a materially different delivery returns 409 with IDEMPOTENCY_KEY_REUSED; an exact repeat of the same delivery is idempotent and returns the original without consuming another daily delivery.

404 instead of 403 on a specific resource is intentional: the API does not reveal whether an ID exists in another organization.

On this page