> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cr3dentials.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> What each SDK error means, what to show the user, and what to fix.

`runTree` either returns a `TreeSummary` or throws one error: `SdkError` in Swift, `SdkException` in Kotlin. Both have the same cases.

> [!WARNING]
>
> **Never retry automatically.** The SDK doesn't, and your app shouldn't retry behind the user's back. After any error, the user chooses whether to start again, and your backend creates a new run for that attempt.

## Error cases

| Case | What to show the user | Start again? |
| - | - | - |
| `Cancelled` | Nothing, or "Cancelled" | When they choose |
| `Transport` | "A network problem. Check your connection." | When they choose |
| `Policy` | "This verification isn't available right now." | Only after the cause is fixed ([below](#policy)) |
| `NotLoggedIn` | "Sign-in was not completed." | When they choose |
| `SessionLost` | "The connection dropped. Please try again." | When they choose |
| `LimitExceeded` | "This verification isn't available right now. Try again later." | Later |
| `InsufficientMemory` | "Close other apps, then try again." | When they choose |
| `WrongEvidence`, `Truncated`, `ExtraResponse`, `Reordered` | "The platform's reply could not be verified." | Once. If it repeats, report it |

A switch over every case, so a new case in a later version fails your build instead of reaching users unhandled:

```swift Swift theme={null}
func message(for error: Error) -> String {
    guard let e = error as? SdkError else { return "Something went wrong." }
    switch e {
    case .Cancelled: return "Cancelled."
    case .Transport: return "A network problem. Check your connection."
    case .Policy: return "This verification isn't available right now."
    case .NotLoggedIn: return "Sign-in was not completed."
    case .SessionLost: return "The connection dropped. Please try again."
    case .LimitExceeded: return "This verification isn't available right now. Try again later."
    case .InsufficientMemory: return "Your phone is short of memory. Close other apps, then try again."
    case .WrongEvidence, .Truncated, .ExtraResponse, .Reordered:
        return "The platform's reply could not be verified."
    }
}
```

```kotlin Kotlin theme={null}
fun message(e: SdkException): String = when (e) {
    is SdkException.Cancelled -> "Cancelled."
    is SdkException.Transport -> "A network problem. Check your connection."
    is SdkException.Policy -> "This verification isn't available right now."
    is SdkException.NotLoggedIn -> "Sign-in was not completed."
    is SdkException.SessionLost -> "The connection dropped. Please try again."
    is SdkException.LimitExceeded -> "This verification isn't available right now. Try again later."
    is SdkException.InsufficientMemory -> "Your phone is short of memory. Close other apps, then try again."
    is SdkException.WrongEvidence, is SdkException.Truncated,
    is SdkException.ExtraResponse, is SdkException.Reordered -> "The platform's reply could not be verified."
}
```

## Reading the error text

`Transport` and `Policy` carry a text that says what went wrong. Match on its prefix and log it. Never log tokens or cookies.

```swift Swift theme={null}
} catch SdkError.Policy(let text) where text.hasPrefix("navigation_refused:") {
    show("The verification ended because the page left the platform. Please try again.")
} catch let e as SdkError {
    show(message(for: e))
}
```

```kotlin Kotlin theme={null}
} catch (e: SdkException.Policy) {
    log("policy: ${e.v1}")                       // e.v1, not e.message ("v1=<text>")
    show(message(e))
}
```

## Common `Transport` texts

| Text starts with | Meaning | What to do |
| - | - | - |
| `attester error: attester busy`, `attester error: attester not ready` | The attester is at capacity, or still starting. Nothing was handed off | The user starts again in a minute or two |
| `the allocation named no attester` | No attester was ready | The user starts again later |
| `allocate returned 401`, `allocate returned 403` | The front door refused your `appCredential` | Check your configuration |
| `allocate returned 429` | A per-user or per-app quota | The user starts again later |
| `the attester did not accept the upload within 120 s`, `the attester closed the connection…` | The hand-off didn't finish. The attester may still post a result | The user starts again. Your backend may still receive a result for the old run |

## `Policy`

A `Policy` error means the run's configuration or signed documents refused the run. Retrying doesn't help until the cause is fixed. Most are refused before any page opens, so the user sees nothing.

| Text starts with | Cause | Fix |
| - | - | - |
| `our API has no pack for platform` | The `platformId` has no published pack | Pass the run's `packId` exactly. Contact us if it repeats |
| `pack: the operator signature does not verify`, `pack: the operator key …` | `trustOperatorKey` is wrong or malformed | Check your configuration |
| `trust bundle:` | The trust bundle is malformed, unsigned, or older than one the app already accepted | Fetch it fresh before each run, and check `trustOperatorKey` |
| `enclave not pinned by the trust bundle` | Your copy of the trust bundle predates the enclave's current key | Pass `trustBundleUrl`. Report it if it repeats with `(with the trust bundle refetched)` |
| `policy bundle` | The policy bundle is malformed, or isn't the one whose digest you pinned | Fetch it fresh, and check `policyBundleDigest` |
| `result_url must be …`, `result_token must be …` | The run's `resultUrl` or `accessToken` didn't reach the SDK intact | Pass both exactly as your backend received them |
| `params_json …` | The run's inputs aren't a JSON object, or are over 16 KiB | Pass the check's `params` as JSON text, or `""` |
| `navigation_refused: <host>` | The page tried to load a host the pack doesn't allow, such as a link the user tapped | The user starts again. Report it with the host if it repeats, because the pack may need that sign-in provider |
| `origin_refused: <host>` | The run needed to read the platform while the page was on another host | The user starts again. Report it with the host if it repeats |
| `attester refused the upload:` | The attester's verdict on this run. A new run would get the same answer | Report it with the text |

The full list of texts is in the [SDK's error reference](https://github.com/Cr3dentials/zktls-sdk-releases/blob/HEAD/docs/errors.md).

## A result that did not arrive

`runTree` can return normally while its result never reached Cr3dentials. Then `delivered` is `false`, `handedOff` is `false`, and `deliveryError` says why:

| `deliveryError` | Meaning |
| - | - |
| `the result URL is unreachable` | The phone couldn't reach Cr3dentials |
| `the result URL answered 401` | The run's token was wrong, or the run had already ended (expired or terminated) |
| `the result URL answered 409` | This run's result, or this attested read, was already submitted |
| `the result URL answered 413` | The result was too large |
| `the result URL answered 422` | The attester's claim did not verify |

The data isn't kept on the phone, so the user starts a new run. Tell your backend to terminate the old one.

After a hand-off, `handedOff` is `true` and `delivered` is `false`. That's expected: the attester posts the result itself, and your webhook tells you how the run ended.

## Getting help

Email [info@cr3dentials.xyz](mailto:info@cr3dentials.xyz) with the SDK version, the device and OS, the error case and its text, the time, and the run's `sessionId`. Never send cookies, tokens or user data.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.