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

# Troubleshooting: QR code render failures (400, 422, or 502)

> Decode the three QR endpoints' failure classes — an undialable phone 400, the pseudo-scheme payload 400, a 422 validation split, or a transient 502 QR_RENDER_FAILED with its single retry-safe attempt. Cause table, pre-checks, and the escalation bundle.

# Troubleshooting: QR code render failures

The three QR endpoints — `GET /api/v1/qr/generate`, `GET /api/v1/qr/whatsapp`,
and `GET /api/v1/qr/sms` — can refuse a request at three layers (payload
policy, schema, or phone validation) or fail at the render step itself. Each
layer returns a different status, and only one of them is retry-safe. Match
your status code to the class first, then fix the cause.

If the request returned a QR image but the encoded content looks wrong, that
is a payload-construction question, not a failure — see the
[QR generation model](/concepts/qr-code-generation-model) concept page for
how each endpoint assembles its payload.

## Symptom map

| Status / code | Failure class | Where it fires |
| - | - | - |
| **400 `INVALID_PHONE`** (whatsapp / sms) | Undialable phone number — the `phone` value is not a real dialable destination | Phone validation runs before render on both phone endpoints |
| **400 `VALIDATION_ERROR`** (generate) | Pseudo-scheme payload — `data` starts with a script-execution scheme such as `javascript:`, `data:`, `vbscript:`, `file:`, `about:`, or `blob:` | Payload safety filter on `/qr/generate` |
| **422 `VALIDATION_ERROR`** | Malformed query parts — a parameter fails its schema shape (wrong `size` range, missing `data` / `phone`, oversized `message`) | Schema validation on all three endpoints |
| **502 `QR_RENDER_FAILED`** | Render engine failure — validation passed, the PNG encoder failed | The render step on all three endpoints |

Read `error.code` on the JSON envelope to confirm the class; a `png` default
response means you see these envelopes only when you pass `format=json` or
when the request failed before any image was produced.

## Cause → fix

| Cause | Fix |
| - | - |
| Undialable phone (400 `INVALID_PHONE`) | Pre-check the number with `GET /api/v1/numbers/lookup/{phoneNumber}` or an E.164 formatter before encoding. Fix `phone` to a real international number (`+15551234567`) and re-send once — a repeat of the same value fails identically. |
| Pseudo-scheme payload (400 `VALIDATION_ERROR` on `/qr/generate`) | The safety filter deliberately refuses `javascript:`, `data:`, `vbscript:`, `file:`, `about:`, and `blob:` because some QR scanners execute them in a hosted webview. Re-encode the payload as plain text or an `https:` URL and re-send once — an unchanged repeat fails identically. |
| Malformed query parts (422 `VALIDATION_ERROR`) | Read `error.details.issues` for the named field. Common offenders: `size` outside the 50–1024 bounds, missing required `data` / `phone`, `message` over 1024 chars, `data` over the 2048-character ceiling, `format` outside `png` / `json`. Correct the named field and re-send once. |
| Render engine failure (502 `QR_RENDER_FAILED`) | Retry ONCE with a short backoff (a few seconds). If the second attempt also 502s, stop — escalate with `meta.request_id` from the error envelope. |

## Pre-checks before you call

Run these before any retry to avoid burning the one retry-safe attempt:

* **Size bounds**: `size` must be an integer between **50 and 1024** pixels
  (default **300**). The output PNG is always square.
* **Payload length**: `data` on `/qr/generate` caps at **2048 characters**
  (the QR version-40 byte ceiling); `message` on the phone endpoints caps at
  **1024 characters**.
* **Phone shape**: `phone` must look like an international number —
  `+` plus digits, spaces, parentheses, dashes, or dots only — and then pass
  the dialable-number check.

## Retry discipline

Deterministic failures never succeed on repeat. Only the 502 is retry-safe.

* **400 and 422 classes**: retry exactly zero times until the payload
  changes. A repeat of the same request returns the same envelope.
* **502 `QR_RENDER_FAILED`**: retry once with backoff. If it persists, treat
  it as a platform-side fault and escalate.

## What not to do

* **Do not retry the pseudo-scheme rejection.** The filter is a security
  policy, not a transient gate — re-encoding to an allowed scheme is the
  only fix.
* **Do not loop the 502.** One retry is the shape defined here; a client
  that retries in a tight loop turns a render-side fault into a rate-limit
  problem on top.
* **Do not bypass the 422 with a raw client.** If your integration retries
  422s, strip that path — the envelope's `details.issues` tells you exactly
  which field to correct.

## Escalation bundle

Open a support ticket when the 502 `QR_RENDER_FAILED` persists after one
retry. Include:

1. **Tenant ID** (dashboard → Settings → Organization; API →
   `GET /api/v1/me` returns it as `organizationId`)
2. **`meta.request_id`** from the failing envelope
3. **Endpoint** (`/qr/generate`, `/qr/whatsapp`, or `/qr/sms`)
4. **Parameter summary** — the `size`, `format`, and whether `phone` or
   `data` was involved (do not paste the full payload if it contains
   customer data)

## See also

* [QR generation model](/concepts/qr-code-generation-model) — what each
  endpoint encodes and the payload safety model
* [Click-to-chat QR codes for print and web](/guides/qr-codes) — the
  walkthrough for the `whatsapp` / `sms` deep-link patterns
* [Developer QR Code Tools](/guides/qr-tools) — the dashboard preview page
  for testing payloads before you ship them
* [QR API reference](/api-reference/qr) — the endpoint-level contract
* [Error Code Reference](/reference/error-codes) — the full envelope
  vocabulary the codes above come from


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