> ## 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: caller-ID verification rejects

> Decode the four reject codes the caller-ID register → confirm flow returns — VERIFICATION_REJECTED, CHALLENGE_INVALID, INVALID_CALLER_ID, and ALREADY_VERIFIED — and fix each one at the step that produces it.

# Troubleshooting: caller-ID verification rejects

The caller-ID verification journey is two calls — register sends a one-time
code to the handset, confirm submits it — and every reject it can return is
one of four codes. The full flow, status lifecycle, and resend and revoke
endpoints are on [Manage outbound caller IDs](/guides/voice-caller-ids); this
runbook decodes what each reject means and where the fix lives.

Reject-safety first: `ALREADY_VERIFIED` is deterministic steering, not a
failure of your retry loop — treat it as "already done". The other three are
check-side outcomes; of them, only `CHALLENGE_INVALID` on a mismatched code
is safe to retry, and only until the attempts counter or `expires_at` runs
out.

## The four codes

| Code                    | HTTP | Step that returns it                                                                                            | Deterministic or per-attempt                                                       |
| ----------------------- | ---- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `INVALID_CALLER_ID`     | 422  | Dial-time and register-side acceptance of the number                                                            | Deterministic — fix the number, not the retry                                      |
| `ALREADY_VERIFIED`      | 409  | Register, and confirm on a settled row                                                                          | Deterministic — the number is already verified; not a retryable error              |
| `VERIFICATION_REJECTED` | 422  | Confirm, when the OTP check's terminal status is not approved                                                   | Per-check — the row stays pending, so a resend or a fresh register is the recovery |
| `CHALLENGE_INVALID`     | 400  | Per-attempt challenge validation on Verify challenge endpoints (OTP code mismatch, nonce, or possession phrase) | Per-attempt — retry burns one attempt from the challenge's budget                  |

## `INVALID_CALLER_ID` — the number fails acceptance

The number you passed failed acceptance — either E.164 shape (digits with an
optional leading `+`, 8–20 characters) at register, or the ownership and
verification check the outbound gate applies at dial time.

* **Shape** — normalize to E.164 before calling
  `POST /api/v1/voice/caller-ids/verify`; the leading `+` may be omitted and
  is added back, but anything else malformed returns `422` before a challenge
  is dispatched.
* **Ownership at dial time** — a dial refused because the presented number is
  neither an org-hosted number nor a verified caller ID returns
  `UNVERIFIED_CALLER_ID` instead; the fix is to register and confirm here
  first, then present. See the
  [caller-ID FAQ entries](/reference/faq#number-identity--caller-id) for both
  the dial-time gate and the SIT-trunk
  `VOICE_CALLER_ID_REJECTED` variant.

Retries with the same malformed number return the same code — fix the number
at the data layer.

## `ALREADY_VERIFIED` — a no-op, not an error

Two paths return this, both as `409`:

* **Register** — a `verified` row already exists for the number. Re-verifying
  would burn an OTP dispatch and a wallet charge for nothing; the short-
  circuit is the platform saving you both. If you registered the number
  again under a different friendly name, revoke the existing row first, then
  re-register.
* **Confirm** — the row bound to the `verification_id` is already
  `verified`. Your earlier confirm succeeded; the response was lost
  somewhere, but the state is settled. Read the current status from
  `GET /api/v1/voice/caller-ids` instead of re-submitting the code.

Treat `409 ALREADY_VERIFIED` as an idempotent "already done" outcome. It
belongs on the deterministic-steering side of your error handling — surface
the row, don't retry the call.

## `VERIFICATION_REJECTED` — the check closed without approval

The confirm call reached the OTP check and the check's terminal status was
not approved — most commonly `expired` (the challenge outlived its TTL) or
`failed` (the attempts budget ran out). The submitted code itself may have
been correct; the challenge was already closed when it arrived.

Recovery is at the challenge layer, deterministically:

1. **Resend while the row is still pending** — the row is left in `pending`
   when the check rejects, so
   `POST /api/v1/voice/caller-ids/:id/resend` mints a fresh code on the same
   row without re-running register.
2. **Re-register after expiry** — once the row flips to `expired`, only a
   fresh register (or resend before expiry) re-arms it.

The message in the error envelope carries the check's terminal status
(`status=expired`, `status=failed`) — read that before choosing resend vs.
re-register. Driving your UI's countdown from the `expires_at` register
returned is the clean fix for stops on the third try.

## `CHALLENGE_INVALID` — the per-attempt failure

The shared Verify challenge surfaces mint a challenge, then consume it one
attempt at a time — an OTP code, a nonce, or a spoken possession phrase —
and the per-attempt mismatch rejects with `400 CHALLENGE_INVALID`. On
caller-ID verify specifically that means the submitted code
`verification_id` ↔ code pair did not match on that attempt.

Each attempt decrements the challenge's attempts budget; confirm reads the
row only after the check, so budget exhaustion surfaces through
`VERIFICATION_REJECTED` above. Retry is safe only while
`attempts_remaining` (and `expires_at`) still hold — honour them in your UI
so a user sees the countdown instead of a surprise hard-stop on the last
try.

## Deciding retry-safe vs deterministic

| Outcome                                                                       | Safe to retry?     | Recovery                                                                         |
| ----------------------------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------------- |
| `INVALID_CALLER_ID` (422)                                                     | No                 | Fix the number or ownership, then re-register                                    |
| `ALREADY_VERIFIED` (409)                                                      | Not a failure      | Read the row; revoke only if re-registration is the point                        |
| `VERIFICATION_REJECTED` (422)                                                 | Not the confirm    | Resend on a pending row, or re-register after expiry                             |
| `CHALLENGE_INVALID` (400)                                                     | Yes, within budget | Re-submit the correct code while attempts and TTL remain                         |
| Register-side pre-send gates (balance, rate caps, cooldowns, paused outbound) | Per the gate       | Top up, honour the window, or resume sending — the register call tells you which |

The last row is the register-side dispatch outcome — an OTP that never
leaves the platform because a pre-send gate tripped. Work the
[gates tripped by rate limits and outbound state](/troubleshooting/verify-otp)
page when register fails before a challenge ever dispatches.

## Ticket-grade sample

A fix never needs support until the four codes stop matching their fix.
When they do, include:

1. **Tenant id** — from `GET /api/v1/me`, or Settings → Organization in the
   dashboard.
2. **The reject code and its step** — e.g. `VERIFICATION_REJECTED` on
   confirm, not "verify failed".
3. **The numbers at confirm** — the `verification_id` (from the register
   response), the caller-ID row id (`cid_…`), and the terminal status the
   error message carried.
4. **The number as passed** — the E.164 string as it hit register.
5. **Attempts and expiry** — `attempts_remaining` if you surfaced it, and
   the register-returned `expires_at` for a "stopped on the third try"
   report.

That set lets support trace the register → confirm path without a back-and-
forth, and separates a dispatch-side miss from a check-side reject.

## See also

* [Manage outbound caller IDs](/guides/voice-caller-ids) — the full
  register → confirm flow, the status lifecycle, resend and revoke.
* [Troubleshoot Verify OTP failures](/troubleshooting/verify-otp) — every
  `VERIFY_*` send-side gate and the check-status semantics.
* [Error codes reference](/reference/error-codes) — the platform-wide error
  enum.
* [Caller-ID FAQ entries](/reference/faq#number-identity--caller-id) — the
  dial-time `UNVERIFIED_CALLER_ID` gate and the trunk-side
  `VOICE_CALLER_ID_REJECTED` variant.
