# Errors and troubleshooting

Recover from iframe, authentication, credential, and embedding failures.

Source: https://zetapad.com/developers/docs/troubleshooting

## Iframe does not load

Check the app document's HTTP response headers. `frame-ancestors` must allow both Zetapad production origins. Remove conflicting `X-Frame-Options: DENY` and `SAMEORIGIN` headers. A CSP meta tag cannot configure `frame-ancestors`.

Check the final URL after redirects. Its origin must match the registered launch origin. Local HTTP previews may need an HTTPS tunnel inside an HTTPS parent. See [embedding](https://zetapad.com/developers/docs/embedding).

## Room waits for readiness

Install the listener before calling `ready`. Send the version 2 request envelope, empty params, and a unique request ID. Check the exact target origin, reply origin, and `event.source === window.parent`.

Zetapad shows retry controls after 15 seconds without readiness. The browser example's individual request timeout is two minutes to allow time for consent; that example timeout is not the room's readiness timer.

## Protocol errors

| Code | Recovery |
| --- | --- |
| `CONSENT_DENIED` | Member cancelled; stop initialization. Zetapad closes the room. |
| `AUTHENTICATION_REQUIRED` | The bridge could not issue an approved code; stop initialization and obtain consent again. |
| `ACCESS_REVOKED` | Reopen after a registration update, then authenticate and obtain new consent if requested. |
| `STALE_LAUNCH` | Reopen the room and complete a new `ready` handshake. |
| `INVALID_TOKEN` | Reopen the room if its launch expired; obtain fresh authentication if issued access expired. |
| `REQUEST_IN_PROGRESS` | Wait for the pending authentication or consent request. |
| `DUPLICATE_REQUEST` | Use a new request ID; do not replay the old request. |
| `APP_UNAVAILABLE` | Check publication, deletion, and whether the current member may preview the app; reopen when available. |
| `API_ERROR` | The bridge could not classify the failure; stop initialization and retry when services recover. |
| `INTERNAL_SERVER_ERROR` | Temporary backend failure; stop the current operation and retry later. |

Malformed and untrusted requests receive no response. Inspect your envelope, allowed params, registered origin, and source window if a request times out.

## Where to read member identity

Version 2 `authenticate` returns only `code`, `expiresIn`, and `state`. Your backend must exchange the code with its client ID and secret, then call userinfo to retrieve the verified `userId` and approved fields. This applies even with `scopes: []`. Complete the [authentication flow](https://zetapad.com/developers/docs/authentication) before starting the app.

## REST errors

`INVALID_CLIENT` requires checking the backend's client ID and current secret. Do not retry with a secret that was rotated or deleted.

`INVALID_TOKEN` requires a fresh code or token. Authorization codes expire after 60 seconds and are consumed once. Access tokens expire after 15 minutes. There is no refresh-token endpoint.

`ACCESS_REVOKED` and `APP_UNAVAILABLE` require ending protected app access until the member can reopen and authenticate. Registration saves, unpublishing, deletion, rotation, and disconnection invalidate issued access immediately. A scope or launch-origin change also requires fresh member consent; follow [scope changes and reauthentication](https://zetapad.com/developers/docs/authentication#scope-changes-and-reauthentication).

`BAD_USER_INPUT` requires fixing the token request JSON. `INTERNAL_SERVER_ERROR` is a temporary service failure. Handle errors by their stable codes, not message text.

## App login fails in an iframe

Confirm that the state challenge and login request use the same server-side visitor session. A mismatched, expired, or consumed state must reject login. The browser must never supply its own server-session identity.

Check your app's embedded session mechanism. Secure cross-site cookies generally require `SameSite=None`, but browsers may restrict them. Zetapad's own authentication does not depend on third-party cookies; your app's session configuration still matters.

## Direct access still works

`frame-ancestors` alone does not block a top-level browser tab. Apply the document guard and browser startup check in [restrict app access](https://zetapad.com/developers/docs/access-control), including to nested routes.

If direct API requests work without login, add a current server-session and userinfo check to every protected handler. A valid `ready` result, query parameter, referrer, or browser-supplied profile cannot authorize backend access.
